@massu/core 1.13.0 → 1.14.0
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/CHANGELOG.md +44 -2
- package/commands/_shared-preamble.md +15 -10
- package/commands/_shared-references/verification-table.md +9 -5
- package/commands/massu-checkpoint.md +31 -15
- package/commands/massu-data.md +24 -10
- package/commands/massu-golden-path/references/phase-5.5-production-verify.md +1 -1
- package/commands/massu-production-verify.md +7 -2
- package/commands/massu-rollback.md +26 -8
- package/commands/massu-security.md +29 -10
- package/dist/cli.js +983 -224
- package/dist/hooks/classify-failure.js +46 -0
- package/dist/hooks/cost-tracker.js +46 -0
- package/dist/hooks/incident-pipeline.js +46 -0
- package/dist/hooks/post-tool-use.js +46 -0
- package/dist/hooks/pre-compact.js +46 -0
- package/dist/hooks/quality-event.js +46 -0
- package/dist/hooks/session-end.js +702 -5
- package/dist/hooks/session-start.js +612 -56
- package/dist/hooks/user-prompt.js +296 -14
- package/package.json +3 -3
- package/commands/massu-autoresearch/references/eval-runner.md +0 -84
- package/commands/massu-autoresearch/references/safety-rails.md +0 -125
- package/commands/massu-autoresearch/references/scoring-protocol.md +0 -151
- package/commands/massu-autoresearch.md +0 -258
- package/commands/massu-command-health.md +0 -132
- package/commands/massu-command-improve.md +0 -232
- package/commands/massu-infra-audit.md +0 -187
|
@@ -201,17 +201,17 @@ var init_manifest_registry = __esm({
|
|
|
201
201
|
});
|
|
202
202
|
|
|
203
203
|
// src/detect/package-detector.ts
|
|
204
|
-
import { readFileSync as
|
|
205
|
-
import { join, relative } from "path";
|
|
204
|
+
import { readFileSync as readFileSync3, existsSync as existsSync4, statSync, lstatSync, readdirSync } from "fs";
|
|
205
|
+
import { join as join2, relative } from "path";
|
|
206
206
|
import { parse as parseToml } from "smol-toml";
|
|
207
207
|
function safeRead(path) {
|
|
208
208
|
try {
|
|
209
|
-
if (!
|
|
209
|
+
if (!existsSync4(path)) return null;
|
|
210
210
|
const ls = lstatSync(path);
|
|
211
211
|
if (ls.isSymbolicLink()) return null;
|
|
212
212
|
const st = statSync(path);
|
|
213
213
|
if (!st.isFile()) return null;
|
|
214
|
-
return
|
|
214
|
+
return readFileSync3(path, "utf-8");
|
|
215
215
|
} catch {
|
|
216
216
|
return null;
|
|
217
217
|
}
|
|
@@ -242,7 +242,7 @@ function parsePackageJson(path, directory, root, warnings) {
|
|
|
242
242
|
const peer = Object.keys(
|
|
243
243
|
pkg.peerDependencies ?? {}
|
|
244
244
|
);
|
|
245
|
-
const hasTs = deps.includes("typescript") || devDeps.includes("typescript") ||
|
|
245
|
+
const hasTs = deps.includes("typescript") || devDeps.includes("typescript") || existsSync4(join2(directory, "tsconfig.json"));
|
|
246
246
|
const language = hasTs ? "typescript" : "javascript";
|
|
247
247
|
const scripts = Object.keys(
|
|
248
248
|
pkg.scripts ?? {}
|
|
@@ -668,8 +668,8 @@ function detectManifestsInDir(dir, root, warnings) {
|
|
|
668
668
|
let dirEntries = null;
|
|
669
669
|
for (const entry of getManifestRegistry2()) {
|
|
670
670
|
if (!entry.pattern.startsWith("*")) {
|
|
671
|
-
const path =
|
|
672
|
-
if (!
|
|
671
|
+
const path = join2(dir, entry.pattern);
|
|
672
|
+
if (!existsSync4(path)) continue;
|
|
673
673
|
const m = entry.parse(path, dir, root, warnings);
|
|
674
674
|
if (m !== null) out.push(m);
|
|
675
675
|
} else {
|
|
@@ -682,8 +682,8 @@ function detectManifestsInDir(dir, root, warnings) {
|
|
|
682
682
|
}
|
|
683
683
|
for (const fname of dirEntries) {
|
|
684
684
|
if (!matchManifestPattern2(fname, entry.pattern)) continue;
|
|
685
|
-
const path =
|
|
686
|
-
if (!
|
|
685
|
+
const path = join2(dir, fname);
|
|
686
|
+
if (!existsSync4(path)) continue;
|
|
687
687
|
const m = entry.parse(path, dir, root, warnings);
|
|
688
688
|
if (m !== null) out.push(m);
|
|
689
689
|
}
|
|
@@ -693,7 +693,7 @@ function detectManifestsInDir(dir, root, warnings) {
|
|
|
693
693
|
}
|
|
694
694
|
function listSubdirs(dir) {
|
|
695
695
|
try {
|
|
696
|
-
return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS.has(e.name)).map((e) =>
|
|
696
|
+
return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS.has(e.name)).map((e) => join2(dir, e.name));
|
|
697
697
|
} catch {
|
|
698
698
|
return [];
|
|
699
699
|
}
|
|
@@ -703,8 +703,8 @@ function detectPackageManifests(projectRoot) {
|
|
|
703
703
|
const manifests = [];
|
|
704
704
|
manifests.push(...detectManifestsInDir(projectRoot, projectRoot, warnings));
|
|
705
705
|
for (const ws of WORKSPACE_DIRS) {
|
|
706
|
-
const wsRoot =
|
|
707
|
-
if (!
|
|
706
|
+
const wsRoot = join2(projectRoot, ws);
|
|
707
|
+
if (!existsSync4(wsRoot)) continue;
|
|
708
708
|
for (const sub of listSubdirs(wsRoot)) {
|
|
709
709
|
manifests.push(...detectManifestsInDir(sub, projectRoot, warnings));
|
|
710
710
|
for (const sub2 of listSubdirs(sub)) {
|
|
@@ -6890,38 +6890,38 @@ var init_source_dir_detector = __esm({
|
|
|
6890
6890
|
});
|
|
6891
6891
|
|
|
6892
6892
|
// src/detect/monorepo-detector.ts
|
|
6893
|
-
import { readFileSync as
|
|
6894
|
-
import { join as
|
|
6893
|
+
import { readFileSync as readFileSync4, existsSync as existsSync5, statSync as statSync2, lstatSync as lstatSync2, readdirSync as readdirSync2 } from "fs";
|
|
6894
|
+
import { join as join3, relative as relative2 } from "path";
|
|
6895
6895
|
import { parse as parseYaml2 } from "yaml";
|
|
6896
6896
|
import { parse as parseToml2 } from "smol-toml";
|
|
6897
6897
|
function safeReadText(path) {
|
|
6898
6898
|
try {
|
|
6899
|
-
if (!
|
|
6899
|
+
if (!existsSync5(path)) return null;
|
|
6900
6900
|
const ls = lstatSync2(path);
|
|
6901
6901
|
if (ls.isSymbolicLink()) return null;
|
|
6902
6902
|
const st = statSync2(path);
|
|
6903
6903
|
if (!st.isFile()) return null;
|
|
6904
|
-
return
|
|
6904
|
+
return readFileSync4(path, "utf-8");
|
|
6905
6905
|
} catch {
|
|
6906
6906
|
return null;
|
|
6907
6907
|
}
|
|
6908
6908
|
}
|
|
6909
6909
|
function firstManifestIn(dir) {
|
|
6910
6910
|
for (const m of MANIFEST_PRIORITY) {
|
|
6911
|
-
if (
|
|
6911
|
+
if (existsSync5(join3(dir, m))) return m;
|
|
6912
6912
|
}
|
|
6913
6913
|
return null;
|
|
6914
6914
|
}
|
|
6915
6915
|
function manifestName(dir, manifest) {
|
|
6916
6916
|
try {
|
|
6917
6917
|
if (manifest === "package.json") {
|
|
6918
|
-
const raw = safeReadText(
|
|
6918
|
+
const raw = safeReadText(join3(dir, "package.json"));
|
|
6919
6919
|
if (!raw) return null;
|
|
6920
6920
|
const pkg = JSON.parse(raw);
|
|
6921
6921
|
return typeof pkg.name === "string" ? pkg.name : null;
|
|
6922
6922
|
}
|
|
6923
6923
|
if (manifest === "pyproject.toml") {
|
|
6924
|
-
const raw = safeReadText(
|
|
6924
|
+
const raw = safeReadText(join3(dir, "pyproject.toml"));
|
|
6925
6925
|
if (!raw) return null;
|
|
6926
6926
|
const toml = parseToml2(raw);
|
|
6927
6927
|
const project = toml.project;
|
|
@@ -6932,7 +6932,7 @@ function manifestName(dir, manifest) {
|
|
|
6932
6932
|
return null;
|
|
6933
6933
|
}
|
|
6934
6934
|
if (manifest === "Cargo.toml") {
|
|
6935
|
-
const raw = safeReadText(
|
|
6935
|
+
const raw = safeReadText(join3(dir, "Cargo.toml"));
|
|
6936
6936
|
if (!raw) return null;
|
|
6937
6937
|
const toml = parseToml2(raw);
|
|
6938
6938
|
const pkg = toml.package;
|
|
@@ -6940,7 +6940,7 @@ function manifestName(dir, manifest) {
|
|
|
6940
6940
|
return null;
|
|
6941
6941
|
}
|
|
6942
6942
|
if (manifest === "go.mod") {
|
|
6943
|
-
const raw = safeReadText(
|
|
6943
|
+
const raw = safeReadText(join3(dir, "go.mod"));
|
|
6944
6944
|
if (!raw) return null;
|
|
6945
6945
|
for (const line of raw.split(/\r?\n/)) {
|
|
6946
6946
|
const trimmed = line.trim();
|
|
@@ -6964,7 +6964,7 @@ function pkgFromDir(root, dir) {
|
|
|
6964
6964
|
}
|
|
6965
6965
|
function listSubdirs2(dir) {
|
|
6966
6966
|
try {
|
|
6967
|
-
return readdirSync2(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS2.has(e.name)).map((e) =>
|
|
6967
|
+
return readdirSync2(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS2.has(e.name)).map((e) => join3(dir, e.name));
|
|
6968
6968
|
} catch {
|
|
6969
6969
|
return [];
|
|
6970
6970
|
}
|
|
@@ -6972,8 +6972,8 @@ function listSubdirs2(dir) {
|
|
|
6972
6972
|
function genericWorkspaces(root) {
|
|
6973
6973
|
const out = [];
|
|
6974
6974
|
for (const parent of CONVENTIONAL_WORKSPACE_PARENTS) {
|
|
6975
|
-
const p =
|
|
6976
|
-
if (!
|
|
6975
|
+
const p = join3(root, parent);
|
|
6976
|
+
if (!existsSync5(p)) continue;
|
|
6977
6977
|
for (const sub of listSubdirs2(p)) {
|
|
6978
6978
|
const pkg = pkgFromDir(root, sub);
|
|
6979
6979
|
if (pkg) out.push(pkg);
|
|
@@ -6982,7 +6982,7 @@ function genericWorkspaces(root) {
|
|
|
6982
6982
|
return out;
|
|
6983
6983
|
}
|
|
6984
6984
|
function detectYarnWorkspaces(root) {
|
|
6985
|
-
const raw = safeReadText(
|
|
6985
|
+
const raw = safeReadText(join3(root, "package.json"));
|
|
6986
6986
|
if (!raw) return null;
|
|
6987
6987
|
let pkg;
|
|
6988
6988
|
try {
|
|
@@ -6997,7 +6997,7 @@ function detectYarnWorkspaces(root) {
|
|
|
6997
6997
|
return expandWorkspaceGlobs(root, globs);
|
|
6998
6998
|
}
|
|
6999
6999
|
function detectPnpmWorkspaces(root) {
|
|
7000
|
-
const raw = safeReadText(
|
|
7000
|
+
const raw = safeReadText(join3(root, "pnpm-workspace.yaml"));
|
|
7001
7001
|
if (!raw) return null;
|
|
7002
7002
|
try {
|
|
7003
7003
|
const parsed = parseYaml2(raw);
|
|
@@ -7013,8 +7013,8 @@ function expandWorkspaceGlobs(root, globs) {
|
|
|
7013
7013
|
for (const pattern of globs) {
|
|
7014
7014
|
const parts = pattern.split("/");
|
|
7015
7015
|
if (parts.length === 2 && (parts[1] === "*" || parts[1] === "**")) {
|
|
7016
|
-
const parent =
|
|
7017
|
-
if (!
|
|
7016
|
+
const parent = join3(root, parts[0]);
|
|
7017
|
+
if (!existsSync5(parent)) continue;
|
|
7018
7018
|
for (const sub of listSubdirs2(parent)) {
|
|
7019
7019
|
const pkg = pkgFromDir(root, sub);
|
|
7020
7020
|
if (pkg && !seen.has(pkg.path)) {
|
|
@@ -7024,8 +7024,8 @@ function expandWorkspaceGlobs(root, globs) {
|
|
|
7024
7024
|
}
|
|
7025
7025
|
continue;
|
|
7026
7026
|
}
|
|
7027
|
-
const direct =
|
|
7028
|
-
if (
|
|
7027
|
+
const direct = join3(root, pattern);
|
|
7028
|
+
if (existsSync5(direct)) {
|
|
7029
7029
|
const pkg = pkgFromDir(root, direct);
|
|
7030
7030
|
if (pkg && !seen.has(pkg.path)) {
|
|
7031
7031
|
seen.add(pkg.path);
|
|
@@ -7036,16 +7036,16 @@ function expandWorkspaceGlobs(root, globs) {
|
|
|
7036
7036
|
return out;
|
|
7037
7037
|
}
|
|
7038
7038
|
function hasTurbo(root) {
|
|
7039
|
-
return
|
|
7039
|
+
return existsSync5(join3(root, "turbo.json"));
|
|
7040
7040
|
}
|
|
7041
7041
|
function hasNx(root) {
|
|
7042
|
-
return
|
|
7042
|
+
return existsSync5(join3(root, "nx.json"));
|
|
7043
7043
|
}
|
|
7044
7044
|
function hasLerna(root) {
|
|
7045
|
-
return
|
|
7045
|
+
return existsSync5(join3(root, "lerna.json"));
|
|
7046
7046
|
}
|
|
7047
7047
|
function hasBazel(root) {
|
|
7048
|
-
return
|
|
7048
|
+
return existsSync5(join3(root, "WORKSPACE")) || existsSync5(join3(root, "WORKSPACE.bazel")) || existsSync5(join3(root, "MODULE.bazel"));
|
|
7049
7049
|
}
|
|
7050
7050
|
function detectMonorepo(projectRoot) {
|
|
7051
7051
|
const nested = [];
|
|
@@ -7236,8 +7236,8 @@ var init_vr_command_map = __esm({
|
|
|
7236
7236
|
});
|
|
7237
7237
|
|
|
7238
7238
|
// src/detect/domain-inferrer.ts
|
|
7239
|
-
import { existsSync as
|
|
7240
|
-
import { join as
|
|
7239
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
|
|
7240
|
+
import { join as join4 } from "path";
|
|
7241
7241
|
function titleCase(s) {
|
|
7242
7242
|
if (!s) return s;
|
|
7243
7243
|
return s.split(/[-_\s]+/).filter(Boolean).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
|
|
@@ -7257,8 +7257,8 @@ function topLevelSrcSubdirs(root, sourceDirs) {
|
|
|
7257
7257
|
const effective = sourceDirs.length > 0 ? sourceDirs : ["src"];
|
|
7258
7258
|
const seen = /* @__PURE__ */ new Set();
|
|
7259
7259
|
for (const rel of effective) {
|
|
7260
|
-
const abs =
|
|
7261
|
-
if (!
|
|
7260
|
+
const abs = join4(root, rel);
|
|
7261
|
+
if (!existsSync6(abs)) continue;
|
|
7262
7262
|
try {
|
|
7263
7263
|
for (const e of readdirSync3(abs, { withFileTypes: true })) {
|
|
7264
7264
|
if (!e.isDirectory()) continue;
|
|
@@ -7345,8 +7345,8 @@ var init_domain_inferrer = __esm({
|
|
|
7345
7345
|
});
|
|
7346
7346
|
|
|
7347
7347
|
// src/detect/regex-fallback.ts
|
|
7348
|
-
import { existsSync as
|
|
7349
|
-
import { resolve as resolve4, join as
|
|
7348
|
+
import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
7349
|
+
import { resolve as resolve4, join as join5, basename as basename2 } from "path";
|
|
7350
7350
|
function introspectPython(detection, projectRoot) {
|
|
7351
7351
|
const sourceDir = resolveSourceDir(detection, "python", projectRoot);
|
|
7352
7352
|
if (!sourceDir) return null;
|
|
@@ -7513,9 +7513,9 @@ function resolveSourceDir(detection, lang, projectRoot) {
|
|
|
7513
7513
|
if (list.length > 0) {
|
|
7514
7514
|
const first = list[0];
|
|
7515
7515
|
const abs = resolve4(projectRoot, first);
|
|
7516
|
-
return
|
|
7516
|
+
return existsSync7(abs) ? abs : null;
|
|
7517
7517
|
}
|
|
7518
|
-
return
|
|
7518
|
+
return existsSync7(projectRoot) ? projectRoot : null;
|
|
7519
7519
|
}
|
|
7520
7520
|
function sampleFiles(dir, nameRegex, pathFilter) {
|
|
7521
7521
|
const out = [];
|
|
@@ -7535,7 +7535,7 @@ function sampleFiles(dir, nameRegex, pathFilter) {
|
|
|
7535
7535
|
if (entry === "__pycache__") continue;
|
|
7536
7536
|
if (entry === "venv" || entry === ".venv") continue;
|
|
7537
7537
|
if (entry === "dist" || entry === "build") continue;
|
|
7538
|
-
const child =
|
|
7538
|
+
const child = join5(path, entry);
|
|
7539
7539
|
let st;
|
|
7540
7540
|
try {
|
|
7541
7541
|
st = statSync3(child);
|
|
@@ -7560,7 +7560,7 @@ function readSafe(path) {
|
|
|
7560
7560
|
try {
|
|
7561
7561
|
const st = statSync3(path);
|
|
7562
7562
|
if (st.size > MAX_FILE_BYTES) return null;
|
|
7563
|
-
return
|
|
7563
|
+
return readFileSync5(path, "utf-8");
|
|
7564
7564
|
} catch {
|
|
7565
7565
|
return null;
|
|
7566
7566
|
}
|
|
@@ -7809,7 +7809,7 @@ __export(drift_exports, {
|
|
|
7809
7809
|
computeFingerprint: () => computeFingerprint,
|
|
7810
7810
|
detectDrift: () => detectDrift
|
|
7811
7811
|
});
|
|
7812
|
-
import { createHash } from "crypto";
|
|
7812
|
+
import { createHash as createHash2 } from "crypto";
|
|
7813
7813
|
function summarizeDetection(det) {
|
|
7814
7814
|
const languages = Array.from(new Set(det.manifests.map((m) => m.language))).sort();
|
|
7815
7815
|
const frameworks = {};
|
|
@@ -7840,7 +7840,7 @@ function summarizeDetection(det) {
|
|
|
7840
7840
|
function computeFingerprint(det) {
|
|
7841
7841
|
const data = summarizeDetection(det);
|
|
7842
7842
|
const stable = JSON.stringify(data, Object.keys(data).sort());
|
|
7843
|
-
return
|
|
7843
|
+
return createHash2("sha256").update(stable).digest("hex");
|
|
7844
7844
|
}
|
|
7845
7845
|
function stringOf(v) {
|
|
7846
7846
|
if (typeof v === "string") return v;
|
|
@@ -8988,6 +8988,45 @@ function initMemorySchema(db) {
|
|
|
8988
8988
|
CREATE INDEX IF NOT EXISTS idx_so_file ON shared_observations(file_path);
|
|
8989
8989
|
CREATE INDEX IF NOT EXISTS idx_so_module ON shared_observations(module);
|
|
8990
8990
|
`);
|
|
8991
|
+
db.exec(`
|
|
8992
|
+
CREATE TABLE IF NOT EXISTS team_promotion_outbound (
|
|
8993
|
+
prompt_hash TEXT PRIMARY KEY,
|
|
8994
|
+
destination TEXT NOT NULL,
|
|
8995
|
+
draft_text TEXT NOT NULL,
|
|
8996
|
+
score REAL,
|
|
8997
|
+
signals_json TEXT NOT NULL DEFAULT '[]',
|
|
8998
|
+
content_hash TEXT NOT NULL,
|
|
8999
|
+
-- PA3-004 (Phase 3 Stream A): hardened-destination publish carries the
|
|
9000
|
+
-- publisher's review attestation so the server CHECK (hardened rows need a
|
|
9001
|
+
-- review_attestation) is satisfiable. hardened=0 for the Phase-2 rows.
|
|
9002
|
+
hardened INTEGER NOT NULL DEFAULT 0,
|
|
9003
|
+
review_attestation_json TEXT,
|
|
9004
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
9005
|
+
);
|
|
9006
|
+
CREATE TABLE IF NOT EXISTS team_revocation_outbound (
|
|
9007
|
+
prompt_hash TEXT PRIMARY KEY,
|
|
9008
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
9009
|
+
);
|
|
9010
|
+
`);
|
|
9011
|
+
db.exec(`
|
|
9012
|
+
CREATE TABLE IF NOT EXISTS rule_promotion_events_outbound (
|
|
9013
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
9014
|
+
prompt_hash TEXT NOT NULL,
|
|
9015
|
+
event_type TEXT NOT NULL,
|
|
9016
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
9017
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
9018
|
+
);
|
|
9019
|
+
CREATE INDEX IF NOT EXISTS idx_rpe_outbound_created ON rule_promotion_events_outbound(created_at);
|
|
9020
|
+
`);
|
|
9021
|
+
db.exec(`
|
|
9022
|
+
CREATE TABLE IF NOT EXISTS analytics_events (
|
|
9023
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
9024
|
+
event_type TEXT NOT NULL,
|
|
9025
|
+
event_data TEXT NOT NULL DEFAULT '{}',
|
|
9026
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
9027
|
+
);
|
|
9028
|
+
CREATE INDEX IF NOT EXISTS idx_analytics_events_type ON analytics_events(event_type);
|
|
9029
|
+
`);
|
|
8991
9030
|
db.exec(`
|
|
8992
9031
|
CREATE TABLE IF NOT EXISTS knowledge_conflicts (
|
|
8993
9032
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -9068,6 +9107,13 @@ function initMemorySchema(db) {
|
|
|
9068
9107
|
`ALTER TABLE license_cache ADD COLUMN signed_payload_json TEXT NOT NULL DEFAULT ''`
|
|
9069
9108
|
);
|
|
9070
9109
|
}
|
|
9110
|
+
const outboundCols = db.prepare(`PRAGMA table_info(team_promotion_outbound)`).all();
|
|
9111
|
+
if (!outboundCols.some((c) => c.name === "hardened")) {
|
|
9112
|
+
db.exec(`ALTER TABLE team_promotion_outbound ADD COLUMN hardened INTEGER NOT NULL DEFAULT 0`);
|
|
9113
|
+
}
|
|
9114
|
+
if (!outboundCols.some((c) => c.name === "review_attestation_json")) {
|
|
9115
|
+
db.exec(`ALTER TABLE team_promotion_outbound ADD COLUMN review_attestation_json TEXT`);
|
|
9116
|
+
}
|
|
9071
9117
|
db.exec(`
|
|
9072
9118
|
CREATE TABLE IF NOT EXISTS failure_classes (
|
|
9073
9119
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -9088,6 +9134,22 @@ function initMemorySchema(db) {
|
|
|
9088
9134
|
CREATE INDEX IF NOT EXISTS idx_fc_needs_review ON failure_classes(needs_review);
|
|
9089
9135
|
`);
|
|
9090
9136
|
}
|
|
9137
|
+
function getMemoryMeta(db, key) {
|
|
9138
|
+
const row = db.prepare("SELECT value FROM memory_meta WHERE key = ?").get(key);
|
|
9139
|
+
return row ? row.value : null;
|
|
9140
|
+
}
|
|
9141
|
+
function setMemoryMeta(db, key, value) {
|
|
9142
|
+
db.prepare("INSERT OR REPLACE INTO memory_meta (key, value) VALUES (?, ?)").run(key, value);
|
|
9143
|
+
}
|
|
9144
|
+
function recordTelemetry(db, eventType, data) {
|
|
9145
|
+
try {
|
|
9146
|
+
db.prepare(`
|
|
9147
|
+
INSERT INTO analytics_events (event_type, event_data, created_at)
|
|
9148
|
+
VALUES (?, ?, datetime('now'))
|
|
9149
|
+
`).run(eventType, JSON.stringify(data));
|
|
9150
|
+
} catch {
|
|
9151
|
+
}
|
|
9152
|
+
}
|
|
9091
9153
|
function autoDetectTaskId(planFile) {
|
|
9092
9154
|
if (!planFile) return null;
|
|
9093
9155
|
const base = basename(planFile);
|
|
@@ -9165,9 +9227,499 @@ function linkSessionToTask(db, sessionId, taskId) {
|
|
|
9165
9227
|
db.prepare("UPDATE sessions SET task_id = ? WHERE session_id = ?").run(taskId, sessionId);
|
|
9166
9228
|
}
|
|
9167
9229
|
|
|
9230
|
+
// src/team-rule-sync.ts
|
|
9231
|
+
import { existsSync as existsSync3, writeFileSync, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
|
|
9232
|
+
import { join, dirname as dirname3 } from "path";
|
|
9233
|
+
|
|
9234
|
+
// src/license.ts
|
|
9235
|
+
import { createHash } from "crypto";
|
|
9236
|
+
|
|
9237
|
+
// src/security/ed25519-envelope-verifier.ts
|
|
9238
|
+
import { createPublicKey, verify as cryptoVerify } from "crypto";
|
|
9239
|
+
var SPKI_ED25519_PREFIX = Buffer.from([
|
|
9240
|
+
48,
|
|
9241
|
+
42,
|
|
9242
|
+
48,
|
|
9243
|
+
5,
|
|
9244
|
+
6,
|
|
9245
|
+
3,
|
|
9246
|
+
43,
|
|
9247
|
+
101,
|
|
9248
|
+
112,
|
|
9249
|
+
3,
|
|
9250
|
+
33,
|
|
9251
|
+
0
|
|
9252
|
+
]);
|
|
9253
|
+
function verifyEd25519SignedEnvelope(key, payload) {
|
|
9254
|
+
if (!key.knownFingerprints.has(key.fingerprintHex)) {
|
|
9255
|
+
return {
|
|
9256
|
+
kind: "error",
|
|
9257
|
+
reason: `Bundled ${key.keyLabel} pubkey fingerprint ${key.fingerprintHex} is not in the trusted allowlist. Possible build-time tamper.`
|
|
9258
|
+
};
|
|
9259
|
+
}
|
|
9260
|
+
const sig = payload._signature;
|
|
9261
|
+
const alg = payload._signature_alg;
|
|
9262
|
+
const payloadKeys = payload._signature_payload_keys;
|
|
9263
|
+
const sigPubkey = payload._signature_pubkey_fingerprint;
|
|
9264
|
+
if (typeof sig !== "string" || sig.length === 0) {
|
|
9265
|
+
return { kind: "missing_signature" };
|
|
9266
|
+
}
|
|
9267
|
+
if (alg !== "ed25519") {
|
|
9268
|
+
return { kind: "error", reason: `Unsupported signature algorithm: ${alg}` };
|
|
9269
|
+
}
|
|
9270
|
+
if (!Array.isArray(payloadKeys) || payloadKeys.length === 0) {
|
|
9271
|
+
return { kind: "error", reason: "Missing _signature_payload_keys" };
|
|
9272
|
+
}
|
|
9273
|
+
if (typeof sigPubkey === "string" && sigPubkey !== key.fingerprintHex) {
|
|
9274
|
+
return { kind: "unknown_pubkey", got: sigPubkey };
|
|
9275
|
+
}
|
|
9276
|
+
const canonicalObj = {};
|
|
9277
|
+
for (const k of payloadKeys) {
|
|
9278
|
+
if (typeof k !== "string") continue;
|
|
9279
|
+
canonicalObj[k] = payload[k];
|
|
9280
|
+
}
|
|
9281
|
+
const canonical = JSON.stringify(canonicalObj, [...payloadKeys].sort());
|
|
9282
|
+
try {
|
|
9283
|
+
const der = Buffer.concat([SPKI_ED25519_PREFIX, Buffer.from(key.pubkeyBytes)]);
|
|
9284
|
+
const pubkey = createPublicKey({ key: der, format: "der", type: "spki" });
|
|
9285
|
+
const ok = cryptoVerify(
|
|
9286
|
+
null,
|
|
9287
|
+
Buffer.from(canonical, "utf-8"),
|
|
9288
|
+
pubkey,
|
|
9289
|
+
Buffer.from(sig, "base64")
|
|
9290
|
+
);
|
|
9291
|
+
return ok ? { kind: "valid" } : { kind: "bad_signature" };
|
|
9292
|
+
} catch (err) {
|
|
9293
|
+
return {
|
|
9294
|
+
kind: "error",
|
|
9295
|
+
reason: `Signature verification threw: ${err instanceof Error ? err.message : String(err)}`
|
|
9296
|
+
};
|
|
9297
|
+
}
|
|
9298
|
+
}
|
|
9299
|
+
|
|
9300
|
+
// src/security/license-pubkey.generated.ts
|
|
9301
|
+
var LICENSE_PUBKEY_ED25519 = new Uint8Array([39, 136, 108, 146, 85, 233, 119, 252, 223, 226, 123, 155, 234, 168, 200, 150, 36, 249, 174, 6, 130, 146, 125, 196, 136, 224, 202, 150, 53, 228, 114, 15]);
|
|
9302
|
+
var LICENSE_PUBKEY_FINGERPRINT_HEX = "18a63d64fdec9e5a368fc45feaa49bed6ced815967e582bc7b8af534f22a9475";
|
|
9303
|
+
var KNOWN_LICENSE_PUBKEY_FINGERPRINTS = /* @__PURE__ */ new Set([
|
|
9304
|
+
"18a63d64fdec9e5a368fc45feaa49bed6ced815967e582bc7b8af534f22a9475"
|
|
9305
|
+
]);
|
|
9306
|
+
|
|
9307
|
+
// src/security/license-response-verifier.ts
|
|
9308
|
+
function verifyLicenseResponse(payload) {
|
|
9309
|
+
return verifyEd25519SignedEnvelope(
|
|
9310
|
+
{
|
|
9311
|
+
pubkeyBytes: LICENSE_PUBKEY_ED25519,
|
|
9312
|
+
fingerprintHex: LICENSE_PUBKEY_FINGERPRINT_HEX,
|
|
9313
|
+
knownFingerprints: KNOWN_LICENSE_PUBKEY_FINGERPRINTS,
|
|
9314
|
+
keyLabel: "license"
|
|
9315
|
+
},
|
|
9316
|
+
payload
|
|
9317
|
+
);
|
|
9318
|
+
}
|
|
9319
|
+
function isLicenseSignatureRequired() {
|
|
9320
|
+
return process.env.MASSU_REQUIRE_SIGNED_LICENSE === "true";
|
|
9321
|
+
}
|
|
9322
|
+
|
|
9323
|
+
// src/license.ts
|
|
9324
|
+
var _warnedLicenseSig = false;
|
|
9325
|
+
function warnLicenseSigOnce(reason) {
|
|
9326
|
+
if (_warnedLicenseSig) return;
|
|
9327
|
+
_warnedLicenseSig = true;
|
|
9328
|
+
process.stderr.write(
|
|
9329
|
+
`[massu] WARNING: license-validate response is unsigned or signature invalid (${reason}). Acceptance permitted under transition mode. Operator: provision Supabase Edge Function LICENSE_RESPONSE_SIGNING_PRIVATE_KEY_B64 then set MASSU_REQUIRE_SIGNED_LICENSE=true to enforce strict mode.
|
|
9330
|
+
`
|
|
9331
|
+
);
|
|
9332
|
+
}
|
|
9333
|
+
var TIER_LEVELS = {
|
|
9334
|
+
free: 0,
|
|
9335
|
+
pro: 1,
|
|
9336
|
+
team: 2,
|
|
9337
|
+
enterprise: 3
|
|
9338
|
+
};
|
|
9339
|
+
function tierLevel(tier) {
|
|
9340
|
+
return TIER_LEVELS[tier] ?? 0;
|
|
9341
|
+
}
|
|
9342
|
+
var PLAN_TO_TIER_MAP = {
|
|
9343
|
+
free: "free",
|
|
9344
|
+
cloud_pro: "pro",
|
|
9345
|
+
cloud_team: "team",
|
|
9346
|
+
cloud_enterprise: "enterprise"
|
|
9347
|
+
};
|
|
9348
|
+
var IN_MEMORY_CACHE_TTL_MS = 15 * 60 * 1e3;
|
|
9349
|
+
var GRACE_PERIOD_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
9350
|
+
function readTrustedCache(cached) {
|
|
9351
|
+
if (!cached.signed_payload_json) {
|
|
9352
|
+
if (isLicenseSignatureRequired()) return null;
|
|
9353
|
+
warnLicenseSigOnce("cache_unsigned_transition");
|
|
9354
|
+
return {
|
|
9355
|
+
tier: cached.tier,
|
|
9356
|
+
validUntil: cached.valid_until,
|
|
9357
|
+
features: JSON.parse(cached.features || "[]")
|
|
9358
|
+
};
|
|
9359
|
+
}
|
|
9360
|
+
let parsed;
|
|
9361
|
+
try {
|
|
9362
|
+
parsed = JSON.parse(cached.signed_payload_json);
|
|
9363
|
+
} catch {
|
|
9364
|
+
return null;
|
|
9365
|
+
}
|
|
9366
|
+
const result = verifyLicenseResponse(parsed);
|
|
9367
|
+
if (result.kind !== "valid") return null;
|
|
9368
|
+
const verifiedPlan = typeof parsed.plan === "string" ? parsed.plan : null;
|
|
9369
|
+
const verifiedTierField = typeof parsed.tier === "string" ? parsed.tier : null;
|
|
9370
|
+
const tier = verifiedPlan ? PLAN_TO_TIER_MAP[verifiedPlan] ?? "free" : verifiedTierField ?? "free";
|
|
9371
|
+
const validUntil = typeof parsed.validUntil === "string" ? parsed.validUntil : "";
|
|
9372
|
+
const features = Array.isArray(parsed.features) ? parsed.features : [];
|
|
9373
|
+
const orgId = typeof parsed.orgId === "string" && parsed.orgId.length > 0 ? parsed.orgId : void 0;
|
|
9374
|
+
return { tier, validUntil, features, orgId };
|
|
9375
|
+
}
|
|
9376
|
+
function getCachedTierReadOnly(memDb) {
|
|
9377
|
+
const config = getConfig();
|
|
9378
|
+
const apiKey = config.cloud?.apiKey;
|
|
9379
|
+
if (!apiKey) return "free";
|
|
9380
|
+
const ownsDb = !memDb;
|
|
9381
|
+
const db = memDb ?? getMemoryDb();
|
|
9382
|
+
try {
|
|
9383
|
+
const keyHash = createHash("sha256").update(apiKey).digest("hex");
|
|
9384
|
+
const cached = db.prepare(
|
|
9385
|
+
"SELECT tier, valid_until, last_validated, features, signed_payload_json FROM license_cache WHERE api_key_hash = ?"
|
|
9386
|
+
).get(keyHash);
|
|
9387
|
+
if (!cached) return "free";
|
|
9388
|
+
const trusted = readTrustedCache(cached);
|
|
9389
|
+
if (!trusted) return "free";
|
|
9390
|
+
const lastValidated = new Date(cached.last_validated);
|
|
9391
|
+
const sevenDaysAgo = new Date(Date.now() - GRACE_PERIOD_MS);
|
|
9392
|
+
if (!(lastValidated > sevenDaysAgo)) return "free";
|
|
9393
|
+
return trusted.tier;
|
|
9394
|
+
} catch {
|
|
9395
|
+
return "free";
|
|
9396
|
+
} finally {
|
|
9397
|
+
if (ownsDb) {
|
|
9398
|
+
try {
|
|
9399
|
+
db.close();
|
|
9400
|
+
} catch {
|
|
9401
|
+
}
|
|
9402
|
+
}
|
|
9403
|
+
}
|
|
9404
|
+
}
|
|
9405
|
+
function getCachedOrgId(memDb) {
|
|
9406
|
+
const config = getConfig();
|
|
9407
|
+
const apiKey = config.cloud?.apiKey;
|
|
9408
|
+
if (!apiKey) return null;
|
|
9409
|
+
const ownsDb = !memDb;
|
|
9410
|
+
const db = memDb ?? getMemoryDb();
|
|
9411
|
+
try {
|
|
9412
|
+
const keyHash = createHash("sha256").update(apiKey).digest("hex");
|
|
9413
|
+
const cached = db.prepare(
|
|
9414
|
+
"SELECT tier, valid_until, last_validated, features, signed_payload_json FROM license_cache WHERE api_key_hash = ?"
|
|
9415
|
+
).get(keyHash);
|
|
9416
|
+
if (!cached) return null;
|
|
9417
|
+
const trusted = readTrustedCache(cached);
|
|
9418
|
+
if (!trusted) return null;
|
|
9419
|
+
const lastValidated = new Date(cached.last_validated);
|
|
9420
|
+
const sevenDaysAgo = new Date(Date.now() - GRACE_PERIOD_MS);
|
|
9421
|
+
if (!(lastValidated > sevenDaysAgo)) return null;
|
|
9422
|
+
return trusted.orgId ?? null;
|
|
9423
|
+
} catch {
|
|
9424
|
+
return null;
|
|
9425
|
+
} finally {
|
|
9426
|
+
if (ownsDb) {
|
|
9427
|
+
try {
|
|
9428
|
+
db.close();
|
|
9429
|
+
} catch {
|
|
9430
|
+
}
|
|
9431
|
+
}
|
|
9432
|
+
}
|
|
9433
|
+
}
|
|
9434
|
+
|
|
9435
|
+
// src/auto-learning-entitlement.ts
|
|
9436
|
+
var TEAM_SHARED_PROMOTION_MIN_TIER = "team";
|
|
9437
|
+
function entitledForTeamSharedPromotion(tier) {
|
|
9438
|
+
return tierLevel(tier) >= tierLevel(TEAM_SHARED_PROMOTION_MIN_TIER);
|
|
9439
|
+
}
|
|
9440
|
+
|
|
9441
|
+
// src/security/promotion-pubkey.generated.ts
|
|
9442
|
+
var PROMOTION_PUBKEY_ED25519 = new Uint8Array([107, 161, 33, 17, 189, 44, 193, 128, 252, 155, 188, 236, 100, 163, 23, 146, 219, 155, 216, 139, 134, 72, 211, 182, 151, 122, 209, 151, 135, 65, 167, 26]);
|
|
9443
|
+
var PROMOTION_PUBKEY_FINGERPRINT_HEX = "b14e2a73e23c02891e976ec161d339da6c930266c0202828d3187a3bd6e5d83f";
|
|
9444
|
+
var KNOWN_PROMOTION_PUBKEY_FINGERPRINTS = /* @__PURE__ */ new Set([
|
|
9445
|
+
"b14e2a73e23c02891e976ec161d339da6c930266c0202828d3187a3bd6e5d83f"
|
|
9446
|
+
]);
|
|
9447
|
+
|
|
9448
|
+
// src/security/promotion-envelope-verifier.ts
|
|
9449
|
+
function verifyPromotionEnvelope(payload) {
|
|
9450
|
+
return verifyEd25519SignedEnvelope(
|
|
9451
|
+
{
|
|
9452
|
+
pubkeyBytes: PROMOTION_PUBKEY_ED25519,
|
|
9453
|
+
fingerprintHex: PROMOTION_PUBKEY_FINGERPRINT_HEX,
|
|
9454
|
+
knownFingerprints: KNOWN_PROMOTION_PUBKEY_FINGERPRINTS,
|
|
9455
|
+
keyLabel: "promotion"
|
|
9456
|
+
},
|
|
9457
|
+
payload
|
|
9458
|
+
);
|
|
9459
|
+
}
|
|
9460
|
+
|
|
9461
|
+
// src/rule-candidate-hardened.ts
|
|
9462
|
+
var TEAM_HARDENED_SHAREABLE_DESTINATIONS = [
|
|
9463
|
+
"pattern-scanner",
|
|
9464
|
+
"custom-destination"
|
|
9465
|
+
];
|
|
9466
|
+
function isHardenedShareableDestination(destination) {
|
|
9467
|
+
return TEAM_HARDENED_SHAREABLE_DESTINATIONS.includes(destination);
|
|
9468
|
+
}
|
|
9469
|
+
|
|
9470
|
+
// src/rule-candidate-applier.ts
|
|
9471
|
+
var TEAM_SHAREABLE_DESTINATIONS = [
|
|
9472
|
+
"corrections-md",
|
|
9473
|
+
"claude-md-cr"
|
|
9474
|
+
];
|
|
9475
|
+
function isTeamShareableDestination(destination) {
|
|
9476
|
+
return TEAM_SHAREABLE_DESTINATIONS.includes(destination);
|
|
9477
|
+
}
|
|
9478
|
+
|
|
9479
|
+
// src/team-knowledge.ts
|
|
9480
|
+
function shareObservation(db, developerId, project, observationType, summary, opts) {
|
|
9481
|
+
const result = db.prepare(`
|
|
9482
|
+
INSERT INTO shared_observations
|
|
9483
|
+
(original_id, developer_id, project, observation_type, summary, file_path, module, severity, is_shared, shared_at)
|
|
9484
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, TRUE, datetime('now'))
|
|
9485
|
+
`).run(
|
|
9486
|
+
opts?.originalId ?? null,
|
|
9487
|
+
developerId,
|
|
9488
|
+
project,
|
|
9489
|
+
observationType,
|
|
9490
|
+
summary,
|
|
9491
|
+
opts?.filePath ?? null,
|
|
9492
|
+
opts?.module ?? null,
|
|
9493
|
+
opts?.severity ?? 3
|
|
9494
|
+
);
|
|
9495
|
+
return Number(result.lastInsertRowid);
|
|
9496
|
+
}
|
|
9497
|
+
|
|
9498
|
+
// src/team-rule-sync.ts
|
|
9499
|
+
var CURSOR_KEY = "team_promotions_cursor";
|
|
9500
|
+
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
9501
|
+
var PROMPT_HASH_RE = /^[0-9a-f]{16}$/;
|
|
9502
|
+
var ZERO = {
|
|
9503
|
+
pulled: 0,
|
|
9504
|
+
materialized: 0,
|
|
9505
|
+
skipped: 0,
|
|
9506
|
+
dropped_unverified: 0,
|
|
9507
|
+
dropped_nonshareable: 0,
|
|
9508
|
+
revoked_handled: 0
|
|
9509
|
+
};
|
|
9510
|
+
async function pullTeamPromotions(db, opts = {}) {
|
|
9511
|
+
const config = getConfig();
|
|
9512
|
+
const cloud = config.cloud;
|
|
9513
|
+
const projectRoot = opts.projectRoot ?? getProjectRoot();
|
|
9514
|
+
const tier = opts.tier ?? getCachedTierReadOnly(db);
|
|
9515
|
+
if (!entitledForTeamSharedPromotion(tier)) return { ...ZERO };
|
|
9516
|
+
const endpoint = opts.endpoint ?? cloud?.endpoint;
|
|
9517
|
+
const apiKey = opts.apiKey ?? cloud?.apiKey;
|
|
9518
|
+
if (!endpoint || !apiKey) return { ...ZERO };
|
|
9519
|
+
const ownOrgId = opts.orgId !== void 0 ? opts.orgId : getCachedOrgId(db);
|
|
9520
|
+
const since = parseCursor(getMemoryMeta(db, CURSOR_KEY));
|
|
9521
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
9522
|
+
const timeoutMs = opts.timeoutMs ?? cloud?.requestTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
9523
|
+
let envelope;
|
|
9524
|
+
try {
|
|
9525
|
+
const res = await fetchImpl(`${endpoint}/promoted-rules?since=${since}`, {
|
|
9526
|
+
method: "GET",
|
|
9527
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
9528
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
9529
|
+
});
|
|
9530
|
+
if (!res.ok) return { ...ZERO };
|
|
9531
|
+
envelope = await res.json();
|
|
9532
|
+
} catch {
|
|
9533
|
+
return { ...ZERO };
|
|
9534
|
+
}
|
|
9535
|
+
const verdict = verifyPromotionEnvelope(envelope);
|
|
9536
|
+
if (verdict.kind !== "valid") {
|
|
9537
|
+
const result2 = { ...ZERO, dropped_unverified: countUntrusted(envelope) };
|
|
9538
|
+
emitDropTelemetry(db, "team_promotion_envelope_dropped", { reason: verdict.kind });
|
|
9539
|
+
return result2;
|
|
9540
|
+
}
|
|
9541
|
+
const signedKeys = Array.isArray(envelope._signature_payload_keys) ? envelope._signature_payload_keys : [];
|
|
9542
|
+
if (!signedKeys.includes("orgId") || !signedKeys.includes("promotions_json")) {
|
|
9543
|
+
const result2 = { ...ZERO, dropped_unverified: countUntrusted(envelope) };
|
|
9544
|
+
emitDropTelemetry(db, "team_promotion_unsigned_field", {
|
|
9545
|
+
orgId_signed: signedKeys.includes("orgId"),
|
|
9546
|
+
promotions_json_signed: signedKeys.includes("promotions_json")
|
|
9547
|
+
});
|
|
9548
|
+
return result2;
|
|
9549
|
+
}
|
|
9550
|
+
const signedOrgId = typeof envelope.orgId === "string" ? envelope.orgId : null;
|
|
9551
|
+
if (!signedOrgId || !ownOrgId || signedOrgId !== ownOrgId) {
|
|
9552
|
+
const result2 = { ...ZERO, dropped_unverified: countUntrusted(envelope) };
|
|
9553
|
+
emitDropTelemetry(db, "team_promotion_org_mismatch", {
|
|
9554
|
+
signed_org_present: !!signedOrgId,
|
|
9555
|
+
own_org_present: !!ownOrgId
|
|
9556
|
+
});
|
|
9557
|
+
return result2;
|
|
9558
|
+
}
|
|
9559
|
+
const promotions = parsePromotions(envelope.promotions_json);
|
|
9560
|
+
const result = { ...ZERO };
|
|
9561
|
+
let maxSeq = since;
|
|
9562
|
+
for (const p of promotions) {
|
|
9563
|
+
if (!isValidWirePromotion(p)) continue;
|
|
9564
|
+
result.pulled += 1;
|
|
9565
|
+
if (typeof p.seq === "number" && p.seq > maxSeq) maxSeq = p.seq;
|
|
9566
|
+
const hardenedMaterialize = isHardenedShareableDestination(p.destination) && p.hardened === true;
|
|
9567
|
+
if (!isTeamShareableDestination(p.destination) && !hardenedMaterialize) {
|
|
9568
|
+
result.dropped_nonshareable += 1;
|
|
9569
|
+
continue;
|
|
9570
|
+
}
|
|
9571
|
+
const candidatePath = sidecarPath(projectRoot, p.prompt_hash);
|
|
9572
|
+
if (p.revoked_at) {
|
|
9573
|
+
handleRevocation(db, projectRoot, candidatePath, p.prompt_hash);
|
|
9574
|
+
result.revoked_handled += 1;
|
|
9575
|
+
continue;
|
|
9576
|
+
}
|
|
9577
|
+
if (existsSync3(candidatePath) || alreadyApplied(db, p.prompt_hash)) {
|
|
9578
|
+
result.skipped += 1;
|
|
9579
|
+
continue;
|
|
9580
|
+
}
|
|
9581
|
+
materializeCandidate(db, projectRoot, candidatePath, p, signedOrgId);
|
|
9582
|
+
result.materialized += 1;
|
|
9583
|
+
}
|
|
9584
|
+
const serverCursor = typeof envelope.cursor === "number" ? envelope.cursor : 0;
|
|
9585
|
+
const nextCursor = Math.max(since, maxSeq, serverCursor);
|
|
9586
|
+
if (nextCursor > since) setMemoryMeta(db, CURSOR_KEY, String(nextCursor));
|
|
9587
|
+
return result;
|
|
9588
|
+
}
|
|
9589
|
+
function parseCursor(raw) {
|
|
9590
|
+
if (raw === null) return 0;
|
|
9591
|
+
const n = Number(raw);
|
|
9592
|
+
return Number.isInteger(n) && n >= 0 ? n : 0;
|
|
9593
|
+
}
|
|
9594
|
+
function parsePromotions(json) {
|
|
9595
|
+
if (typeof json !== "string") return [];
|
|
9596
|
+
try {
|
|
9597
|
+
const v = JSON.parse(json);
|
|
9598
|
+
return Array.isArray(v) ? v : [];
|
|
9599
|
+
} catch {
|
|
9600
|
+
return [];
|
|
9601
|
+
}
|
|
9602
|
+
}
|
|
9603
|
+
function countUntrusted(envelope) {
|
|
9604
|
+
const arr = parsePromotions(envelope.promotions_json);
|
|
9605
|
+
return arr.length > 0 ? arr.length : 1;
|
|
9606
|
+
}
|
|
9607
|
+
function isValidWirePromotion(p) {
|
|
9608
|
+
if (!p || typeof p !== "object") return false;
|
|
9609
|
+
const r = p;
|
|
9610
|
+
return typeof r.prompt_hash === "string" && PROMPT_HASH_RE.test(r.prompt_hash) && typeof r.destination === "string" && typeof r.draft_text === "string" && typeof r.promoted_by === "string" && typeof r.promoted_at === "string";
|
|
9611
|
+
}
|
|
9612
|
+
function sidecarPath(projectRoot, promptHash) {
|
|
9613
|
+
return join(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
|
|
9614
|
+
}
|
|
9615
|
+
function alreadyApplied(db, promptHash) {
|
|
9616
|
+
try {
|
|
9617
|
+
const row = db.prepare(
|
|
9618
|
+
`SELECT 1 FROM audit_log WHERE event_type = 'rule_promoted'
|
|
9619
|
+
AND json_extract(metadata, '$.prompt_hash') = ? LIMIT 1`
|
|
9620
|
+
).get(promptHash);
|
|
9621
|
+
return !!row;
|
|
9622
|
+
} catch {
|
|
9623
|
+
return false;
|
|
9624
|
+
}
|
|
9625
|
+
}
|
|
9626
|
+
function handleRevocation(db, projectRoot, candidatePath, promptHash) {
|
|
9627
|
+
if (existsSync3(candidatePath)) {
|
|
9628
|
+
try {
|
|
9629
|
+
unlinkSync(candidatePath);
|
|
9630
|
+
} catch {
|
|
9631
|
+
}
|
|
9632
|
+
return;
|
|
9633
|
+
}
|
|
9634
|
+
if (alreadyApplied(db, promptHash)) {
|
|
9635
|
+
process.stderr.write(
|
|
9636
|
+
`[massu] team rule ${promptHash} was revoked by your org \u2014 consider reverting it.
|
|
9637
|
+
`
|
|
9638
|
+
);
|
|
9639
|
+
}
|
|
9640
|
+
}
|
|
9641
|
+
function materializeCandidate(db, projectRoot, candidatePath, p, orgId) {
|
|
9642
|
+
const promptText = p.draft_text.replace(/\n+/g, " ").slice(0, 200) || `team rule ${p.prompt_hash}`;
|
|
9643
|
+
const sidecar = {
|
|
9644
|
+
// Standard RuleCandidatePayload fields (so `/massu-rule approve` → readCandidate
|
|
9645
|
+
// → validateCandidatePayload passes), synthesized from the promotion.
|
|
9646
|
+
prompt: promptText,
|
|
9647
|
+
prompt_hash: p.prompt_hash,
|
|
9648
|
+
score: clampScore(p.score),
|
|
9649
|
+
signals: sanitizeSignals(p.signals),
|
|
9650
|
+
prior_turn_files: [],
|
|
9651
|
+
timestamp: p.promoted_at,
|
|
9652
|
+
session_id: `team:${p.promoted_by}`,
|
|
9653
|
+
// Provenance (PB-004): the applier's team-origin gate keys on this. PA3-005:
|
|
9654
|
+
// a hardened materialization sets `hardened: true` so the applier's hardened
|
|
9655
|
+
// apply-gate (PA3-004) engages. `review_attestation` is intentionally NOT
|
|
9656
|
+
// copied from the publisher here — the RECEIVER's `/massu-rule review` records
|
|
9657
|
+
// ITS OWN two-operator + render-only ack into provenance.review_attestation
|
|
9658
|
+
// before apply; until then the gate refuses (hardened-PENDING).
|
|
9659
|
+
provenance: {
|
|
9660
|
+
origin: "team",
|
|
9661
|
+
org_id: orgId,
|
|
9662
|
+
promoted_by: p.promoted_by,
|
|
9663
|
+
promoted_at: p.promoted_at,
|
|
9664
|
+
signature_verified: true,
|
|
9665
|
+
...p.hardened === true ? { hardened: true } : {}
|
|
9666
|
+
},
|
|
9667
|
+
// Extra fields the `/massu-rule approve` flow reads to drive the apply (the
|
|
9668
|
+
// publisher already decided destination + body). validateCandidatePayload
|
|
9669
|
+
// ignores unknown keys. `publisher_review_attestation` is retained for the
|
|
9670
|
+
// receiver's review UI (display only — never the apply-gate authority).
|
|
9671
|
+
destination: p.destination,
|
|
9672
|
+
draft_text: p.draft_text,
|
|
9673
|
+
...p.review_attestation !== void 0 ? { publisher_review_attestation: p.review_attestation } : {}
|
|
9674
|
+
};
|
|
9675
|
+
const dir = dirname3(candidatePath);
|
|
9676
|
+
if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
|
|
9677
|
+
writeFileSync(candidatePath, JSON.stringify(sidecar, null, 2), "utf-8");
|
|
9678
|
+
try {
|
|
9679
|
+
shareObservation(db, p.promoted_by, getProjectName(), "rule_promotion", promptText, {
|
|
9680
|
+
filePath: void 0,
|
|
9681
|
+
module: p.destination
|
|
9682
|
+
});
|
|
9683
|
+
} catch {
|
|
9684
|
+
}
|
|
9685
|
+
}
|
|
9686
|
+
function clampScore(score) {
|
|
9687
|
+
if (typeof score !== "number" || !Number.isFinite(score)) return 0;
|
|
9688
|
+
return Math.max(-200, Math.min(200, score));
|
|
9689
|
+
}
|
|
9690
|
+
function sanitizeSignals(signals) {
|
|
9691
|
+
if (!Array.isArray(signals)) return [];
|
|
9692
|
+
const out = [];
|
|
9693
|
+
for (const s of signals) {
|
|
9694
|
+
if (!s || typeof s !== "object") continue;
|
|
9695
|
+
const sig = s;
|
|
9696
|
+
out.push({
|
|
9697
|
+
name: typeof sig.name === "string" ? sig.name : "unknown",
|
|
9698
|
+
baseWeight: typeof sig.baseWeight === "number" ? sig.baseWeight : 0,
|
|
9699
|
+
applied: typeof sig.applied === "number" ? sig.applied : 0,
|
|
9700
|
+
...typeof sig.evidence === "string" ? { evidence: sig.evidence } : {}
|
|
9701
|
+
});
|
|
9702
|
+
}
|
|
9703
|
+
return out;
|
|
9704
|
+
}
|
|
9705
|
+
function getProjectName() {
|
|
9706
|
+
try {
|
|
9707
|
+
return getConfig().project?.name ?? "massu";
|
|
9708
|
+
} catch {
|
|
9709
|
+
return "massu";
|
|
9710
|
+
}
|
|
9711
|
+
}
|
|
9712
|
+
function emitDropTelemetry(db, eventType, data) {
|
|
9713
|
+
recordTelemetry(db, eventType, data);
|
|
9714
|
+
process.stderr.write(
|
|
9715
|
+
`[massu] team-shared promotion pull: dropped envelope (${eventType}). A signed/org-matched response is required \u2014 see massu.ai for details.
|
|
9716
|
+
`
|
|
9717
|
+
);
|
|
9718
|
+
}
|
|
9719
|
+
|
|
9168
9720
|
// src/hooks/session-start.ts
|
|
9169
|
-
import { readFileSync as
|
|
9170
|
-
import { join as
|
|
9721
|
+
import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
|
|
9722
|
+
import { join as join6, resolve as resolve5 } from "path";
|
|
9171
9723
|
import { parse as parseYaml3 } from "yaml";
|
|
9172
9724
|
|
|
9173
9725
|
// src/lib/pidLiveness.ts
|
|
@@ -9246,6 +9798,10 @@ Session memory, code intelligence, and governance are now active.
|
|
|
9246
9798
|
const watcherBanner = buildWatcherBanner();
|
|
9247
9799
|
if (watcherBanner) process.stdout.write(watcherBanner);
|
|
9248
9800
|
}
|
|
9801
|
+
try {
|
|
9802
|
+
await pullTeamPromotions(db);
|
|
9803
|
+
} catch (_pullErr) {
|
|
9804
|
+
}
|
|
9249
9805
|
} finally {
|
|
9250
9806
|
db.close();
|
|
9251
9807
|
}
|
|
@@ -9335,7 +9891,7 @@ async function buildContext(db, sessionId, source, tokenBudget, taskId) {
|
|
|
9335
9891
|
}
|
|
9336
9892
|
try {
|
|
9337
9893
|
const knowledgeDbPath = getResolvedPaths().knowledgeDbPath;
|
|
9338
|
-
if (
|
|
9894
|
+
if (existsSync8(knowledgeDbPath)) {
|
|
9339
9895
|
const Database2 = (await import("better-sqlite3")).default;
|
|
9340
9896
|
const kdb = new Database2(knowledgeDbPath, { readonly: true });
|
|
9341
9897
|
try {
|
|
@@ -9426,8 +9982,8 @@ async function buildDriftBanner() {
|
|
|
9426
9982
|
if (process.env.MASSU_DRIFT_QUIET === "1") return "";
|
|
9427
9983
|
if (watcherIsLiveAndFresh()) return "";
|
|
9428
9984
|
const configPath = resolve5(process.cwd(), "massu.config.yaml");
|
|
9429
|
-
if (!
|
|
9430
|
-
const content =
|
|
9985
|
+
if (!existsSync8(configPath)) return "";
|
|
9986
|
+
const content = readFileSync6(configPath, "utf-8");
|
|
9431
9987
|
const parsed = parseYaml3(content);
|
|
9432
9988
|
if (!parsed || typeof parsed !== "object") return "";
|
|
9433
9989
|
const det = parsed.detection;
|
|
@@ -9467,9 +10023,9 @@ function loadCorrectionsPreventionRules() {
|
|
|
9467
10023
|
const config = getConfig();
|
|
9468
10024
|
const claudeDirName = config.conventions?.claudeDirName ?? ".claude";
|
|
9469
10025
|
const projectDirName = cwd.replace(/[/\\]/g, "-").replace(/^-/, "");
|
|
9470
|
-
const correctionsPath =
|
|
9471
|
-
if (!
|
|
9472
|
-
const content =
|
|
10026
|
+
const correctionsPath = join6(homeDir, claudeDirName, "projects", projectDirName, "memory", "corrections.md");
|
|
10027
|
+
if (!existsSync8(correctionsPath)) return [];
|
|
10028
|
+
const content = readFileSync6(correctionsPath, "utf-8");
|
|
9473
10029
|
const lines = content.split("\n");
|
|
9474
10030
|
const rules = [];
|
|
9475
10031
|
for (const line of lines) {
|
|
@@ -9491,8 +10047,8 @@ function loadCorrectionsPreventionRules() {
|
|
|
9491
10047
|
function readWatchStateRaw(cwd) {
|
|
9492
10048
|
try {
|
|
9493
10049
|
const path = resolve5(cwd, ".massu", "watch-state.json");
|
|
9494
|
-
if (!
|
|
9495
|
-
const obj = JSON.parse(
|
|
10050
|
+
if (!existsSync8(path)) return null;
|
|
10051
|
+
const obj = JSON.parse(readFileSync6(path, "utf-8"));
|
|
9496
10052
|
if (!obj || typeof obj !== "object") return null;
|
|
9497
10053
|
return obj;
|
|
9498
10054
|
} catch {
|