@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,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NoLiveVersionToRevokeError = exports.NoLiveVersionToSupersedeError = exports.CrossScopeLineageError = void 0;
|
|
4
|
+
exports.insertLocalRuleVersion = insertLocalRuleVersion;
|
|
5
|
+
exports.getLocalRuleVersion = getLocalRuleVersion;
|
|
6
|
+
exports.getLiveLocalRuleVersion = getLiveLocalRuleVersion;
|
|
7
|
+
exports.listLiveLocalRuleVersions = listLiveLocalRuleVersions;
|
|
8
|
+
exports.listLocalRuleVersionHistory = listLocalRuleVersionHistory;
|
|
9
|
+
exports.supersedeLiveLocalRuleVersion = supersedeLiveLocalRuleVersion;
|
|
10
|
+
exports.revokeLiveLocalRuleVersion = revokeLiveLocalRuleVersion;
|
|
11
|
+
exports.insertVersionEvaluationRecord = insertVersionEvaluationRecord;
|
|
12
|
+
const interception_store_1 = require("./interception-store");
|
|
13
|
+
/** Raised when a version's lineage pointer would cross a runtime scope (FK lineage must stay in-scope). */
|
|
14
|
+
class CrossScopeLineageError extends Error {
|
|
15
|
+
versionId;
|
|
16
|
+
runtimeScopeId;
|
|
17
|
+
supersedesVersionId;
|
|
18
|
+
constructor(versionId, runtimeScopeId, supersedesVersionId) {
|
|
19
|
+
super(`version ${versionId} in scope ${runtimeScopeId} cannot supersede ${supersedesVersionId}: ` +
|
|
20
|
+
`the predecessor is in a different runtime scope`);
|
|
21
|
+
this.versionId = versionId;
|
|
22
|
+
this.runtimeScopeId = runtimeScopeId;
|
|
23
|
+
this.supersedesVersionId = supersedesVersionId;
|
|
24
|
+
this.name = "CrossScopeLineageError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.CrossScopeLineageError = CrossScopeLineageError;
|
|
28
|
+
/** Raised when a supersession is requested but no LIVE version exists for the (scope, rule) to replace. */
|
|
29
|
+
class NoLiveVersionToSupersedeError extends Error {
|
|
30
|
+
runtimeScopeId;
|
|
31
|
+
ruleId;
|
|
32
|
+
constructor(runtimeScopeId, ruleId) {
|
|
33
|
+
super(`no LIVE version for rule ${ruleId} in scope ${runtimeScopeId} to supersede`);
|
|
34
|
+
this.runtimeScopeId = runtimeScopeId;
|
|
35
|
+
this.ruleId = ruleId;
|
|
36
|
+
this.name = "NoLiveVersionToSupersedeError";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.NoLiveVersionToSupersedeError = NoLiveVersionToSupersedeError;
|
|
40
|
+
/** Raised when a revoke (kill switch) is requested but no LIVE version exists for the (scope, rule). */
|
|
41
|
+
class NoLiveVersionToRevokeError extends Error {
|
|
42
|
+
runtimeScopeId;
|
|
43
|
+
ruleId;
|
|
44
|
+
constructor(runtimeScopeId, ruleId) {
|
|
45
|
+
super(`no LIVE version for rule ${ruleId} in scope ${runtimeScopeId} to revoke`);
|
|
46
|
+
this.runtimeScopeId = runtimeScopeId;
|
|
47
|
+
this.ruleId = ruleId;
|
|
48
|
+
this.name = "NoLiveVersionToRevokeError";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.NoLiveVersionToRevokeError = NoLiveVersionToRevokeError;
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// local_rule_version
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
function mapVersionRow(row) {
|
|
56
|
+
return {
|
|
57
|
+
versionId: row.version_id,
|
|
58
|
+
ruleId: row.rule_id,
|
|
59
|
+
runtimeScopeId: row.runtime_scope_id,
|
|
60
|
+
rulePayload: row.rule_payload,
|
|
61
|
+
canonicalPayloadHash: row.canonical_payload_hash,
|
|
62
|
+
lifecycleStatus: row.lifecycle_status,
|
|
63
|
+
attestationMethod: row.attestation_method,
|
|
64
|
+
attestedBy: row.attested_by,
|
|
65
|
+
supersedesVersionId: row.supersedes_version_id ?? null,
|
|
66
|
+
derivedFromObservedHash: row.derived_from_observed_hash ?? null,
|
|
67
|
+
attestedAt: row.attested_at,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** Insert a version exactly as given. Same-scope FK lineage is verified BEFORE the write; everything
|
|
71
|
+
* else (one-LIVE, payload uniqueness, immutability) is left to the schema. */
|
|
72
|
+
function insertLocalRuleVersion(store, rec) {
|
|
73
|
+
if (rec.supersedesVersionId !== null) {
|
|
74
|
+
const predecessor = getLocalRuleVersionAnyScope(store, rec.supersedesVersionId);
|
|
75
|
+
if (predecessor && predecessor.runtimeScopeId !== rec.runtimeScopeId) {
|
|
76
|
+
throw new CrossScopeLineageError(rec.versionId, rec.runtimeScopeId, rec.supersedesVersionId);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
store.db
|
|
80
|
+
.prepare(`INSERT INTO local_rule_version
|
|
81
|
+
(version_id, rule_id, runtime_scope_id, rule_payload, canonical_payload_hash,
|
|
82
|
+
lifecycle_status, attestation_method, attested_by, supersedes_version_id,
|
|
83
|
+
derived_from_observed_hash, attested_at)
|
|
84
|
+
VALUES
|
|
85
|
+
(@version_id, @rule_id, @runtime_scope_id, @rule_payload, @canonical_payload_hash,
|
|
86
|
+
@lifecycle_status, @attestation_method, @attested_by, @supersedes_version_id,
|
|
87
|
+
@derived_from_observed_hash, @attested_at)`)
|
|
88
|
+
.run({
|
|
89
|
+
version_id: rec.versionId,
|
|
90
|
+
rule_id: rec.ruleId,
|
|
91
|
+
runtime_scope_id: rec.runtimeScopeId,
|
|
92
|
+
rule_payload: rec.rulePayload,
|
|
93
|
+
canonical_payload_hash: rec.canonicalPayloadHash,
|
|
94
|
+
lifecycle_status: rec.lifecycleStatus,
|
|
95
|
+
attestation_method: rec.attestationMethod,
|
|
96
|
+
attested_by: rec.attestedBy,
|
|
97
|
+
supersedes_version_id: rec.supersedesVersionId,
|
|
98
|
+
derived_from_observed_hash: rec.derivedFromObservedHash,
|
|
99
|
+
attested_at: rec.attestedAt,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/** Read one version by the runtime-scope-safe composite key; null if it is absent OR in another scope. */
|
|
103
|
+
function getLocalRuleVersion(store, versionId, runtimeScopeId) {
|
|
104
|
+
const row = store.db
|
|
105
|
+
.prepare(`SELECT * FROM local_rule_version WHERE version_id = ? AND runtime_scope_id = ?`)
|
|
106
|
+
.get(versionId, runtimeScopeId);
|
|
107
|
+
return row ? mapVersionRow(row) : null;
|
|
108
|
+
}
|
|
109
|
+
/** Scope-agnostic lookup used ONLY to validate lineage (a predecessor's scope). Not exported. */
|
|
110
|
+
function getLocalRuleVersionAnyScope(store, versionId) {
|
|
111
|
+
const row = store.db
|
|
112
|
+
.prepare(`SELECT * FROM local_rule_version WHERE version_id = ?`)
|
|
113
|
+
.get(versionId);
|
|
114
|
+
return row ? mapVersionRow(row) : null;
|
|
115
|
+
}
|
|
116
|
+
/** Read the current LIVE version for a (scope, rule); null when none is LIVE. */
|
|
117
|
+
function getLiveLocalRuleVersion(store, runtimeScopeId, ruleId) {
|
|
118
|
+
const row = store.db
|
|
119
|
+
.prepare(`SELECT * FROM local_rule_version
|
|
120
|
+
WHERE runtime_scope_id = ? AND rule_id = ? AND lifecycle_status = 'LIVE'`)
|
|
121
|
+
.get(runtimeScopeId, ruleId);
|
|
122
|
+
return row ? mapVersionRow(row) : null;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* List EVERY LIVE rule version in one scope, ordered by ruleId ascending. This is the rule-driven
|
|
126
|
+
* enforce dispatch's input (R4): the seam evaluates all of these against one tool attempt. The ruleId
|
|
127
|
+
* ordering is the deterministic tie-break the dispatch relies on when more than one rule would deny the
|
|
128
|
+
* same action (it emits the deny of the lowest ruleId and records the rest as arms). One LIVE row per
|
|
129
|
+
* (scope, rule) is guaranteed by ux_one_live_version, so this never returns two versions of one rule.
|
|
130
|
+
*/
|
|
131
|
+
function listLiveLocalRuleVersions(store, runtimeScopeId) {
|
|
132
|
+
const rows = store.db
|
|
133
|
+
.prepare(`SELECT * FROM local_rule_version
|
|
134
|
+
WHERE runtime_scope_id = ? AND lifecycle_status = 'LIVE'
|
|
135
|
+
ORDER BY rule_id`)
|
|
136
|
+
.all(runtimeScopeId);
|
|
137
|
+
return rows.map(mapVersionRow);
|
|
138
|
+
}
|
|
139
|
+
/** List a rule's version history in one scope, oldest first (attested_at then version_id). */
|
|
140
|
+
function listLocalRuleVersionHistory(store, runtimeScopeId, ruleId) {
|
|
141
|
+
const rows = store.db
|
|
142
|
+
.prepare(`SELECT * FROM local_rule_version
|
|
143
|
+
WHERE runtime_scope_id = ? AND rule_id = ?
|
|
144
|
+
ORDER BY attested_at, version_id`)
|
|
145
|
+
.all(runtimeScopeId, ruleId);
|
|
146
|
+
return rows.map(mapVersionRow);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Atomically supersede the current LIVE version of a (scope, rule) with `successor`. In one
|
|
150
|
+
* BEGIN IMMEDIATE transaction: find the current LIVE; demote it to SUPERSEDED (the only lifecycle
|
|
151
|
+
* transition trg_version_immutable allows); insert `successor` as LIVE with supersedes_version_id
|
|
152
|
+
* pointing at the demoted version. Demote-then-insert is deliberate: it never leaves two LIVE rows
|
|
153
|
+
* for the (scope, rule), so ux_one_live_version is satisfied at every statement boundary. Throws
|
|
154
|
+
* NoLiveVersionToSupersedeError when there is nothing LIVE to replace, and refuses a `successor`
|
|
155
|
+
* whose supersedesVersionId is provided but does not name the actual prior LIVE. Returns the minted
|
|
156
|
+
* successor with its lineage filled in.
|
|
157
|
+
*/
|
|
158
|
+
function supersedeLiveLocalRuleVersion(store, successor) {
|
|
159
|
+
const run = store.db.transaction(() => {
|
|
160
|
+
const current = getLiveLocalRuleVersion(store, successor.runtimeScopeId, successor.ruleId);
|
|
161
|
+
if (!current) {
|
|
162
|
+
throw new NoLiveVersionToSupersedeError(successor.runtimeScopeId, successor.ruleId);
|
|
163
|
+
}
|
|
164
|
+
if (successor.supersedesVersionId !== null && successor.supersedesVersionId !== current.versionId) {
|
|
165
|
+
throw new Error(`successor ${successor.versionId} claims to supersede ${successor.supersedesVersionId} ` +
|
|
166
|
+
`but the current LIVE version is ${current.versionId}`);
|
|
167
|
+
}
|
|
168
|
+
store.db
|
|
169
|
+
.prepare(`UPDATE local_rule_version SET lifecycle_status = 'SUPERSEDED' WHERE version_id = ?`)
|
|
170
|
+
.run(current.versionId);
|
|
171
|
+
const minted = {
|
|
172
|
+
...successor,
|
|
173
|
+
lifecycleStatus: "LIVE",
|
|
174
|
+
supersedesVersionId: current.versionId,
|
|
175
|
+
};
|
|
176
|
+
insertLocalRuleVersion(store, minted);
|
|
177
|
+
return minted;
|
|
178
|
+
});
|
|
179
|
+
return run.immediate();
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The kill switch. Atomically disarm a (scope, rule) by flipping its current LIVE version to REVOKED
|
|
183
|
+
* (the LIVE->REVOKED transition trg_version_immutable explicitly permits). After this the (scope, rule)
|
|
184
|
+
* has NO LIVE version, so getLiveLocalRuleVersion returns null and the enforce seam finds NO_LIVE_VERSION
|
|
185
|
+
* and fails open: enforcement stops cleanly without deleting any history. Scoped strictly to the given
|
|
186
|
+
* runtime scope, so a same-rule LIVE version in another scope is never touched. Throws
|
|
187
|
+
* NoLiveVersionToRevokeError when there is nothing LIVE to disarm. Returns the now-REVOKED version.
|
|
188
|
+
*/
|
|
189
|
+
function revokeLiveLocalRuleVersion(store, runtimeScopeId, ruleId) {
|
|
190
|
+
const run = store.db.transaction(() => {
|
|
191
|
+
const current = getLiveLocalRuleVersion(store, runtimeScopeId, ruleId);
|
|
192
|
+
if (!current) {
|
|
193
|
+
throw new NoLiveVersionToRevokeError(runtimeScopeId, ruleId);
|
|
194
|
+
}
|
|
195
|
+
store.db
|
|
196
|
+
.prepare(`UPDATE local_rule_version SET lifecycle_status = 'REVOKED' WHERE version_id = ?`)
|
|
197
|
+
.run(current.versionId);
|
|
198
|
+
return { ...current, lifecycleStatus: "REVOKED" };
|
|
199
|
+
});
|
|
200
|
+
return run.immediate();
|
|
201
|
+
}
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// rule_evaluation_record (version arm)
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
/** Write a version-arm verdict: a rule_evaluation_record bound to a LocalRuleVersion. The observed
|
|
206
|
+
* arm is forced null here, so the schema's arm CHECK can only ever see the version arm; the composite
|
|
207
|
+
* (rule_version_id, runtime_scope_id) FK rejects a verdict that references a version in another scope. */
|
|
208
|
+
function insertVersionEvaluationRecord(store, input) {
|
|
209
|
+
(0, interception_store_1.insertRuleEvaluationRecord)(store, {
|
|
210
|
+
...input,
|
|
211
|
+
observedRuleSnapshot: null,
|
|
212
|
+
observedRuleHash: null,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MEMORY-REQUIREMENT CLASSIFIER (vendored into the CLI for the UserPromptSubmit hook).
|
|
4
|
+
*
|
|
5
|
+
* Given the operator's current prompt, derive whether asserting an answer WITHOUT
|
|
6
|
+
* consulting governed memory plausibly violates an operator expectation. The CE0
|
|
7
|
+
* obligation forms ONLY on a REQUIRED turn, so this classifier is the relevance
|
|
8
|
+
* trigger for CONSULT_GOVERNED_EVIDENCE_ON_MEMORY_REQUIRED_TURN_V1
|
|
9
|
+
* (notes/20260617-evidence-consultation-forcing-function-proposal.md §1.3).
|
|
10
|
+
*
|
|
11
|
+
* Why vendored, not imported: the CLI intentionally does not depend on
|
|
12
|
+
* @meetless/utils (see kb-candidate.ts). This is a byte-faithful copy of the utils
|
|
13
|
+
* classifier (packages/utils/src/memory-requirement.ts); the seed-set versions
|
|
14
|
+
* (raw-prompt-substring-v1 / seed-v1) are pinned so the two implementations cannot
|
|
15
|
+
* silently diverge on which turns are governed. The classification is pure: no hash,
|
|
16
|
+
* no persistence, no runtime. The TurnMemoryAssessment record and the obligation it
|
|
17
|
+
* may create live in the store and the adapter.
|
|
18
|
+
*
|
|
19
|
+
* Three-valued band (R3 P1.2 removed HELPFUL): REQUIRED | NOT_REQUIRED | UNKNOWN.
|
|
20
|
+
* Only REQUIRED creates an obligation (P0.3); NOT_REQUIRED and UNKNOWN are telemetry.
|
|
21
|
+
* A REQUIRED seed marker wins (the seed catches "why did we choose X" even though
|
|
22
|
+
* "why" is itself an excluded generic lead-in); failing that, an EXCLUSION match is
|
|
23
|
+
* what makes a turn CONFIDENTLY NOT_REQUIRED; matching NEITHER is undecidable from
|
|
24
|
+
* shape, so it is UNKNOWN (the population recall improvements later promote).
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.EXCLUSION_MARKERS = exports.REQUIRED_MARKERS = exports.MEMORY_REQUIREMENT_EXCLUSION_SET_VERSION = exports.MEMORY_REQUIREMENT_MARKER_SET_VERSION = exports.MEMORY_REQUIREMENT_CLASSIFIER_VERSION = void 0;
|
|
28
|
+
exports.normalizeForMarkerMatch = normalizeForMarkerMatch;
|
|
29
|
+
exports.classifyMemoryRequirement = classifyMemoryRequirement;
|
|
30
|
+
/** Frozen seed-set versions, stamped on every classification (P1.3 attestation). */
|
|
31
|
+
exports.MEMORY_REQUIREMENT_CLASSIFIER_VERSION = "raw-prompt-substring-v1";
|
|
32
|
+
exports.MEMORY_REQUIREMENT_MARKER_SET_VERSION = "seed-v1";
|
|
33
|
+
exports.MEMORY_REQUIREMENT_EXCLUSION_SET_VERSION = "seed-v1";
|
|
34
|
+
// Sorted for byte-stable identity; de-duplicated by construction.
|
|
35
|
+
const sortedFrozen = (markers) => Object.freeze(Array.from(new Set(markers)).sort());
|
|
36
|
+
/**
|
|
37
|
+
* Seed REQUIRED markers (proposal lines 290-301): high precision, deliberately low
|
|
38
|
+
* recall. A match means the prompt points at workspace-internal decisions, ownership,
|
|
39
|
+
* policy, architecture, or cross-session history.
|
|
40
|
+
*/
|
|
41
|
+
exports.REQUIRED_MARKERS = sortedFrozen([
|
|
42
|
+
"what did we decide",
|
|
43
|
+
"why did we choose",
|
|
44
|
+
"are we still doing",
|
|
45
|
+
"our canonical",
|
|
46
|
+
"previous session",
|
|
47
|
+
"earlier agent",
|
|
48
|
+
"who owns",
|
|
49
|
+
"who approves",
|
|
50
|
+
"our policy",
|
|
51
|
+
"our architecture decision",
|
|
52
|
+
]);
|
|
53
|
+
/**
|
|
54
|
+
* Explicitly EXCLUDED generic-conceptual lead-ins (proposal lines 305-310), stored as
|
|
55
|
+
* their placeholder-free, matchable prefixes. A match (absent any REQUIRED marker)
|
|
56
|
+
* makes the turn CONFIDENTLY NOT_REQUIRED.
|
|
57
|
+
*/
|
|
58
|
+
exports.EXCLUSION_MARKERS = sortedFrozen([
|
|
59
|
+
"why",
|
|
60
|
+
"how does",
|
|
61
|
+
"what is",
|
|
62
|
+
"difference between",
|
|
63
|
+
]);
|
|
64
|
+
/**
|
|
65
|
+
* Normalize text to a space-padded, lowercased, single-spaced token stream so a marker
|
|
66
|
+
* can be matched on token boundaries via plain substring containment (" who owns " is
|
|
67
|
+
* in " who owns this " but not in " whoever owns "). Any run of non-alphanumeric
|
|
68
|
+
* characters becomes a single space; an empty / all-punctuation input reduces to a
|
|
69
|
+
* single pad space.
|
|
70
|
+
*/
|
|
71
|
+
function normalizeForMarkerMatch(text) {
|
|
72
|
+
const core = text
|
|
73
|
+
.toLowerCase()
|
|
74
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
75
|
+
.trim();
|
|
76
|
+
return core === "" ? " " : ` ${core} `;
|
|
77
|
+
}
|
|
78
|
+
/** Which of `markers` appear as whole-token substrings of the padded prompt. */
|
|
79
|
+
function matched(paddedPrompt, markers) {
|
|
80
|
+
return markers.filter((m) => paddedPrompt.includes(` ${m} `));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Classify the current prompt's MemoryRequirement. Pure and deterministic: REQUIRED if
|
|
84
|
+
* any seed marker matches; else NOT_REQUIRED if any exclusion marker matches; else
|
|
85
|
+
* UNKNOWN.
|
|
86
|
+
*/
|
|
87
|
+
function classifyMemoryRequirement(prompt) {
|
|
88
|
+
const padded = normalizeForMarkerMatch(prompt);
|
|
89
|
+
const markersMatched = matched(padded, exports.REQUIRED_MARKERS);
|
|
90
|
+
const exclusionsMatched = matched(padded, exports.EXCLUSION_MARKERS);
|
|
91
|
+
let requirement;
|
|
92
|
+
if (markersMatched.length > 0) {
|
|
93
|
+
requirement = "REQUIRED";
|
|
94
|
+
}
|
|
95
|
+
else if (exclusionsMatched.length > 0) {
|
|
96
|
+
requirement = "NOT_REQUIRED";
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
requirement = "UNKNOWN";
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
requirement,
|
|
103
|
+
markersMatched,
|
|
104
|
+
exclusionsMatched,
|
|
105
|
+
classifierVersion: exports.MEMORY_REQUIREMENT_CLASSIFIER_VERSION,
|
|
106
|
+
markerSetVersion: exports.MEMORY_REQUIREMENT_MARKER_SET_VERSION,
|
|
107
|
+
exclusionSetVersion: exports.MEMORY_REQUIREMENT_EXCLUSION_SET_VERSION,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.observeNotesRule = observeNotesRule;
|
|
4
|
+
const notes_rule_1 = require("./notes-rule");
|
|
5
|
+
const observe_adapter_1 = require("./observe-adapter");
|
|
6
|
+
const notes_path_1 = require("./notes-path");
|
|
7
|
+
const NO_DECISION = {};
|
|
8
|
+
/**
|
|
9
|
+
* Observe a single PreToolUse call against the notes-location pilot. Selects the
|
|
10
|
+
* pilot directive from the scan cache, normalizes it into an ObservedRuleSpec,
|
|
11
|
+
* derives the runtime forbidden-root scope, and hands the call to the observe-only
|
|
12
|
+
* adapter. Always returns an empty (decision-free) response; the verdict travels on
|
|
13
|
+
* the observation side channel.
|
|
14
|
+
*/
|
|
15
|
+
async function observeNotesRule(input) {
|
|
16
|
+
// No notes-location rule declared in this workspace: nothing to observe. This is
|
|
17
|
+
// genuinely NOT_APPLICABLE (absence of a rule), not an infrastructure fault.
|
|
18
|
+
const directive = (0, notes_rule_1.selectNotesLocationDirective)(input.directives);
|
|
19
|
+
if (!directive) {
|
|
20
|
+
return { response: NO_DECISION, observation: { kind: "NOT_APPLICABLE" } };
|
|
21
|
+
}
|
|
22
|
+
const built = (0, notes_rule_1.buildObservedNotesRuleSpec)(directive);
|
|
23
|
+
if (!built.ok) {
|
|
24
|
+
return { response: NO_DECISION, observation: { kind: "INFRA", diagnostic: built.diagnostic } };
|
|
25
|
+
}
|
|
26
|
+
const notesScope = {
|
|
27
|
+
canonicalProjectRoot: input.runtimeProjectRoot,
|
|
28
|
+
configuredRelativeForbiddenPath: built.spec.forbiddenRootRelativePath,
|
|
29
|
+
};
|
|
30
|
+
const config = {
|
|
31
|
+
applicability: built.spec.applicability,
|
|
32
|
+
notesScope,
|
|
33
|
+
classify: input.classify ?? notes_path_1.classifyTargetPath,
|
|
34
|
+
timeoutMs: input.timeoutMs,
|
|
35
|
+
};
|
|
36
|
+
// The adapter runs selection + compliance evaluation and always returns
|
|
37
|
+
// `response: {}`. We pass its result straight through.
|
|
38
|
+
return (0, observe_adapter_1.observePreToolUse)(input.rawStdin, config);
|
|
39
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
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.isUnderRoot = isUnderRoot;
|
|
37
|
+
exports.classifyTargetPath = classifyTargetPath;
|
|
38
|
+
exports.classifyRuntimeTarget = classifyRuntimeTarget;
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const evaluation_input_hash_1 = require("./evaluation-input-hash");
|
|
42
|
+
const moduleCaseCache = new Map();
|
|
43
|
+
/** A tail component must be a single, safe, lexical name (no FS lookup). */
|
|
44
|
+
function isValidTailComponent(component) {
|
|
45
|
+
return (component.length > 0 &&
|
|
46
|
+
component !== "." &&
|
|
47
|
+
component !== ".." &&
|
|
48
|
+
!component.includes("/") &&
|
|
49
|
+
!component.includes(path.sep) &&
|
|
50
|
+
!component.includes("\0"));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Canonicalize an absolute path. Walks up to the nearest existing ancestor,
|
|
54
|
+
* realpaths it (following symlinks), validates each not-yet-existing tail
|
|
55
|
+
* component lexically, and re-appends the tail. Returns null when the path
|
|
56
|
+
* cannot be canonicalized (permission errors, ambiguous `..` past a missing
|
|
57
|
+
* directory, etc.).
|
|
58
|
+
*/
|
|
59
|
+
async function canonicalize(absPath) {
|
|
60
|
+
const tail = [];
|
|
61
|
+
let cur = absPath;
|
|
62
|
+
for (;;) {
|
|
63
|
+
let resolved;
|
|
64
|
+
try {
|
|
65
|
+
resolved = await fs.promises.realpath(cur);
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
const code = err.code;
|
|
69
|
+
if (code !== "ENOENT") {
|
|
70
|
+
// EACCES, ELOOP, ENOTDIR, invalid argument (NUL), etc.: cannot prove.
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const base = path.basename(cur);
|
|
74
|
+
const parent = path.dirname(cur);
|
|
75
|
+
if (parent === cur) {
|
|
76
|
+
// Reached the filesystem root without finding an existing ancestor.
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
if (!isValidTailComponent(base)) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
tail.push(base);
|
|
83
|
+
cur = parent;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const canonical = tail.length > 0 ? path.join(resolved, ...tail.reverse()) : resolved;
|
|
87
|
+
let existingDir;
|
|
88
|
+
try {
|
|
89
|
+
const st = await fs.promises.stat(resolved);
|
|
90
|
+
existingDir = st.isDirectory() ? resolved : path.dirname(resolved);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return { canonical, existingDir };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Read-only per-device case-sensitivity probe: flip the case of the deepest
|
|
100
|
+
* existing directory's name and stat the sibling. Same inode -> case-insensitive;
|
|
101
|
+
* ENOENT -> case-sensitive; anything else (or no alphabetic character to flip)
|
|
102
|
+
* -> undeterminable.
|
|
103
|
+
*/
|
|
104
|
+
function defaultCaseProbe(existingDir) {
|
|
105
|
+
let real;
|
|
106
|
+
try {
|
|
107
|
+
real = fs.realpathSync(existingDir);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const base = path.basename(real);
|
|
113
|
+
const parent = path.dirname(real);
|
|
114
|
+
const flipped = flipCase(base);
|
|
115
|
+
if (flipped === base) {
|
|
116
|
+
// No alphabetic character to flip; cannot probe non-destructively.
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
let origStat;
|
|
120
|
+
try {
|
|
121
|
+
origStat = fs.statSync(real);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const flipStat = fs.statSync(path.join(parent, flipped));
|
|
128
|
+
return flipStat.ino === origStat.ino && flipStat.dev === origStat.dev;
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
if (err.code === "ENOENT") {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function flipCase(name) {
|
|
138
|
+
let out = "";
|
|
139
|
+
for (const ch of name) {
|
|
140
|
+
const lower = ch.toLowerCase();
|
|
141
|
+
const upper = ch.toUpperCase();
|
|
142
|
+
if (lower !== upper) {
|
|
143
|
+
out += ch === lower ? upper : lower;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
out += ch;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Pure prefix comparison under a case policy. A path is "under" the root when it
|
|
153
|
+
* equals the root or is a descendant (boundary-correct: "/a/notes-archive" is
|
|
154
|
+
* NOT under "/a/notes").
|
|
155
|
+
*/
|
|
156
|
+
function isUnderRoot(target, root, caseInsensitive) {
|
|
157
|
+
const t = caseInsensitive ? target.toLowerCase() : target;
|
|
158
|
+
const r = caseInsensitive ? root.toLowerCase() : root;
|
|
159
|
+
if (t === r) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
return t.startsWith(r.endsWith(path.sep) ? r : r + path.sep);
|
|
163
|
+
}
|
|
164
|
+
async function classifyTargetPath(rawFilePath, scope, opts = {}) {
|
|
165
|
+
if (typeof rawFilePath !== "string" || rawFilePath.length === 0) {
|
|
166
|
+
return "INDETERMINATE";
|
|
167
|
+
}
|
|
168
|
+
if (rawFilePath.includes("\0")) {
|
|
169
|
+
return "INDETERMINATE";
|
|
170
|
+
}
|
|
171
|
+
const absTarget = path.isAbsolute(rawFilePath)
|
|
172
|
+
? rawFilePath
|
|
173
|
+
: scope.canonicalProjectRoot + path.sep + rawFilePath;
|
|
174
|
+
const forbiddenRaw = path.isAbsolute(scope.configuredRelativeForbiddenPath)
|
|
175
|
+
? scope.configuredRelativeForbiddenPath
|
|
176
|
+
: path.join(scope.canonicalProjectRoot, scope.configuredRelativeForbiddenPath);
|
|
177
|
+
const target = await canonicalize(absTarget);
|
|
178
|
+
if (!target) {
|
|
179
|
+
return "INDETERMINATE";
|
|
180
|
+
}
|
|
181
|
+
const forbidden = await canonicalize(forbiddenRaw);
|
|
182
|
+
if (!forbidden) {
|
|
183
|
+
return "INDETERMINATE";
|
|
184
|
+
}
|
|
185
|
+
const caseInsensitive = resolveCasePolicy(forbidden.existingDir, opts);
|
|
186
|
+
if (caseInsensitive === null) {
|
|
187
|
+
return "INDETERMINATE";
|
|
188
|
+
}
|
|
189
|
+
return isUnderRoot(target.canonical, forbidden.canonical, caseInsensitive)
|
|
190
|
+
? "UNDER_FORBIDDEN_ROOT"
|
|
191
|
+
: "OUTSIDE_FORBIDDEN_ROOT";
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Classify a target path into the evaluation-input-v1 `target` union (the
|
|
195
|
+
* pathCanonicalizerVersion="notes-path-v1" canonicalizer feeding the persisted
|
|
196
|
+
* snapshot). This is the runtime-SCOPE axis, distinct from the forbidden-root denylist
|
|
197
|
+
* of classifyTargetPath: it answers "where does this action write, relative to the
|
|
198
|
+
* runtime project root?", never leaking an absolute home path.
|
|
199
|
+
*
|
|
200
|
+
* RUNTIME_RELATIVE { path } the target canonicalizes under the runtime root; `path`
|
|
201
|
+
* is the posix, machine-independent relative path.
|
|
202
|
+
* OUTSIDE_RUNTIME_SCOPE the target canonicalizes outside the runtime root; no
|
|
203
|
+
* path is carried (privacy).
|
|
204
|
+
* UNKNOWN / CANONICALIZATION_FAILED canonicalization or the case policy cannot prove
|
|
205
|
+
* the answer (mirrors classifyTargetPath's INDETERMINATE).
|
|
206
|
+
*
|
|
207
|
+
* The stored target plus forbiddenRootRelativePath are sufficient for a later replay to
|
|
208
|
+
* recompute the verdict from the snapshot alone, with no second filesystem probe.
|
|
209
|
+
*/
|
|
210
|
+
async function classifyRuntimeTarget(rawFilePath, runtimeProjectRoot, opts = {}) {
|
|
211
|
+
const unknown = { kind: "UNKNOWN", reasonCode: evaluation_input_hash_1.CANONICALIZATION_FAILED };
|
|
212
|
+
if (typeof rawFilePath !== "string" || rawFilePath.length === 0) {
|
|
213
|
+
return unknown;
|
|
214
|
+
}
|
|
215
|
+
if (rawFilePath.includes("\0")) {
|
|
216
|
+
return unknown;
|
|
217
|
+
}
|
|
218
|
+
const absTarget = path.isAbsolute(rawFilePath)
|
|
219
|
+
? rawFilePath
|
|
220
|
+
: runtimeProjectRoot + path.sep + rawFilePath;
|
|
221
|
+
const target = await canonicalize(absTarget);
|
|
222
|
+
if (!target) {
|
|
223
|
+
return unknown;
|
|
224
|
+
}
|
|
225
|
+
const root = await canonicalize(runtimeProjectRoot);
|
|
226
|
+
if (!root) {
|
|
227
|
+
return unknown;
|
|
228
|
+
}
|
|
229
|
+
const caseInsensitive = resolveCasePolicy(root.existingDir, opts);
|
|
230
|
+
if (caseInsensitive === null) {
|
|
231
|
+
return unknown;
|
|
232
|
+
}
|
|
233
|
+
if (!isUnderRoot(target.canonical, root.canonical, caseInsensitive)) {
|
|
234
|
+
return { kind: "OUTSIDE_RUNTIME_SCOPE" };
|
|
235
|
+
}
|
|
236
|
+
const relative = path.relative(root.canonical, target.canonical);
|
|
237
|
+
return { kind: "RUNTIME_RELATIVE", path: relative.split(path.sep).join("/") };
|
|
238
|
+
}
|
|
239
|
+
function resolveCasePolicy(existingDir, opts) {
|
|
240
|
+
const probe = opts.caseProbe ?? defaultCaseProbe;
|
|
241
|
+
// The process-wide cache is only consulted/written for the default probe. An
|
|
242
|
+
// injected probe is an explicit per-call override: it must neither be shadowed
|
|
243
|
+
// by a cached value nor poison the cache for other callers. A caller that wants
|
|
244
|
+
// a custom probe to be cached can pass its own caseCache.
|
|
245
|
+
const cache = opts.caseCache ?? (opts.caseProbe ? undefined : moduleCaseCache);
|
|
246
|
+
let dev;
|
|
247
|
+
try {
|
|
248
|
+
dev = fs.statSync(existingDir).dev;
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
dev = undefined;
|
|
252
|
+
}
|
|
253
|
+
if (cache && dev !== undefined && cache.has(dev)) {
|
|
254
|
+
return cache.get(dev);
|
|
255
|
+
}
|
|
256
|
+
const result = probe(existingDir);
|
|
257
|
+
if (cache && result !== null && dev !== undefined) {
|
|
258
|
+
cache.set(dev, result);
|
|
259
|
+
}
|
|
260
|
+
return result;
|
|
261
|
+
}
|