@inerrata-corporation/errata 2.0.16 → 2.0.17-dev.3407

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/errata.mjs CHANGED
@@ -139,6 +139,7 @@ var init_castalia = __esm({
139
139
  ROUTING_REASONS = [
140
140
  /** Harness org (CTF lane, e2e battery, canary) — forced org-private. */
141
141
  "harness-clamp",
142
+ "witnessed-vulnerability-private",
142
143
  /** A signed gateway write target named the destination. */
143
144
  "signed-target-public",
144
145
  "signed-target-org",
@@ -1080,6 +1081,12 @@ function identityId(input) {
1080
1081
  }
1081
1082
  case "Claim":
1082
1083
  return canonicalClaimId(input.text, input.scope);
1084
+ case "WitnessedVulnerability":
1085
+ return genericCanonicalId("dvuln", {
1086
+ workspaceId: input.workspaceId,
1087
+ target: input.target.replace(/\\/g, "/").replace(/^\.\//, ""),
1088
+ statement: canonicalClaimId(input.statement.trim())
1089
+ });
1083
1090
  case "DesignProblem":
1084
1091
  return `${DESIGN_PROBLEM_PREFIX}${canonicalClaimId(input.statement.trim())}`.slice(
1085
1092
  0,
@@ -15849,7 +15856,7 @@ var init_edge_rules = __esm({
15849
15856
  // anchor anything at anything, e.g. at a public `Package`, quietly re-creating
15850
15857
  // the code↔collective bridge the membrane exists to prevent.
15851
15858
  ANCHORED_AT: {
15852
- from: ["Problem", "Solution", "RootCause", "Claim"],
15859
+ from: ["Problem", "Vulnerability", "Solution", "RootCause", "Claim"],
15853
15860
  to: ["Symbol"]
15854
15861
  },
15855
15862
  // ── Security spine (v1 taxonomy.ts SECURITY_FORWARD_ONLY commentary) ──
@@ -15894,7 +15901,7 @@ var init_edge_rules = __esm({
15894
15901
  // `Component` joined the target set with OM-agent-anchors: an agent-named
15895
15902
  // component ("React Router") is the same knowledge→named-unit anchor shape as
15896
15903
  // a Tool — the knowledge is ABOUT it, not dependent on it.
15897
- CONCERNS: { from: ["Problem", "Solution", "RootCause", "Claim", "Pattern"], to: ["Tool", "Component"] },
15904
+ CONCERNS: { from: ["Problem", "Vulnerability", "Solution", "RootCause", "Claim", "Pattern"], to: ["Tool", "Component"] },
15898
15905
  OPERATES_ON: { from: ["Algorithm"], to: ["DataStructure"] },
15899
15906
  INVOLVES: { from: ["Problem", "Solution"], to: ["DataStructure"] },
15900
15907
  // ── Taxonomy (Track 4 Stream C: MITRE CWE `ChildOf` → `Weakness —IS_A→ Weakness`) ──
@@ -18005,8 +18012,6 @@ function repairSessionEvidence(input, aliases = /* @__PURE__ */ new Map()) {
18005
18012
  const mapping = new Map(aliases);
18006
18013
  const uuid3 = "[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}";
18007
18014
  const codex = new RegExp(`^codex:(?:rollout-.*?)?(${uuid3})$`, "i");
18008
- const arrays = /* @__PURE__ */ new Set(["sources", "recurrenceSources", "contributors", "inferredContributors", "citeSessions"]);
18009
- const scalars = /* @__PURE__ */ new Set(["discriminatorSource", "originSession", "sessionId", "sourceSession"]);
18010
18015
  const discover = (value, key = "") => {
18011
18016
  if (typeof value === "string" && (arrays.has(key) || scalars.has(key) || key === "citeAnchorHints")) {
18012
18017
  const match2 = value.match(codex);
@@ -18064,9 +18069,13 @@ function repairSessionEvidence(input, aliases = /* @__PURE__ */ new Map()) {
18064
18069
  }
18065
18070
  return repaired;
18066
18071
  }
18072
+ var arrays, scalars, SESSION_EVIDENCE_FIELDS;
18067
18073
  var init_session_evidence = __esm({
18068
18074
  "../../packages/local-graph/src/session-evidence.ts"() {
18069
18075
  "use strict";
18076
+ arrays = /* @__PURE__ */ new Set(["sources", "recurrenceSources", "contributors", "inferredContributors", "citeSessions"]);
18077
+ scalars = /* @__PURE__ */ new Set(["discriminatorSource", "originSession", "sessionId", "sourceSession"]);
18078
+ SESSION_EVIDENCE_FIELDS = [...arrays, ...scalars, "citeAnchorHints"];
18070
18079
  }
18071
18080
  });
18072
18081
 
@@ -18146,7 +18155,7 @@ function rowToEdge(r) {
18146
18155
  function openGraphStore(opts) {
18147
18156
  return new SqliteGraphStore(opts);
18148
18157
  }
18149
- var LOCAL_RULE_OVERRIDES, SCHEMA_VERSION, SCHEMA_SQL, TEMPORAL_INDEX_SQL, FINGERPRINT_INDEXED_LABELS, SqliteGraphStore;
18158
+ var LOCAL_RULE_OVERRIDES, SCHEMA_VERSION, SESSION_EVIDENCE_PREDICATE, SCHEMA_SQL, TEMPORAL_INDEX_SQL, FINGERPRINT_INDEXED_LABELS, SqliteGraphStore;
18150
18159
  var init_store = __esm({
18151
18160
  "../../packages/local-graph/src/store.ts"() {
18152
18161
  "use strict";
@@ -18177,6 +18186,7 @@ var init_store = __esm({
18177
18186
  REGRESSION_OF: { from: ["Problem"], to: ["Problem"] }
18178
18187
  };
18179
18188
  SCHEMA_VERSION = 6;
18189
+ SESSION_EVIDENCE_PREDICATE = SESSION_EVIDENCE_FIELDS.map((field) => `instr(attrs_json, '${JSON.stringify(field)}') > 0`).join(" OR ");
18180
18190
  SCHEMA_SQL = `
18181
18191
  CREATE TABLE IF NOT EXISTS schema_version (
18182
18192
  version INTEGER PRIMARY KEY
@@ -18269,12 +18279,17 @@ CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
18269
18279
  `;
18270
18280
  TEMPORAL_INDEX_SQL = `
18271
18281
  CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
18282
+ CREATE INDEX IF NOT EXISTS nodes_live_count ON nodes(valid_to);
18283
+ CREATE INDEX IF NOT EXISTS nodes_live_recent ON nodes(label, last_updated_at DESC) WHERE valid_to IS NULL;
18284
+ CREATE INDEX IF NOT EXISTS nodes_session_evidence ON nodes(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
18285
+ CREATE INDEX IF NOT EXISTS edges_session_evidence ON edges(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
18272
18286
  -- Covering + partial index for labelFingerprint over the instance labels: the
18273
18287
  -- live-push probe runs it per workspace on a timer, and each hit used to read
18274
18288
  -- its table row. Partial, so it maintains a few thousand rows instead of adding
18275
18289
  -- a write to every node write. Its label list is FINGERPRINT_INDEXED_LABELS.
18276
18290
  DROP INDEX IF EXISTS nodes_live_fingerprint;
18277
- CREATE INDEX IF NOT EXISTS nodes_instance_fingerprint ON nodes(label, last_updated_at, last_reinforced_at_seq, created_at_seq) WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause');
18291
+ DROP INDEX IF EXISTS nodes_instance_fingerprint;
18292
+ CREATE INDEX IF NOT EXISTS nodes_instance_fingerprint_v2 ON nodes(label, last_updated_at, last_reinforced_at_seq, created_at_seq) WHERE valid_to IS NULL AND label IN ('Problem','Vulnerability','Solution','RootCause');
18278
18293
  CREATE INDEX IF NOT EXISTS nodes_logical ON nodes(logical_id, version);
18279
18294
  CREATE INDEX IF NOT EXISTS edges_live ON edges(valid_to);
18280
18295
  -- Expression index on the per-node relPath (stored in attrs_json) so the
@@ -18288,7 +18303,7 @@ CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) W
18288
18303
  -- every edited file of every turn, and a scan parses every Problem's attrs.
18289
18304
  CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_json, '$.anchorHint.path')) WHERE valid_to IS NULL;
18290
18305
  `;
18291
- FINGERPRINT_INDEXED_LABELS = ["Problem", "Solution", "RootCause"];
18306
+ FINGERPRINT_INDEXED_LABELS = ["Problem", "Vulnerability", "Solution", "RootCause"];
18292
18307
  SqliteGraphStore = class {
18293
18308
  db;
18294
18309
  // Typed edge lookups build their SQL from the number of types asked for, and
@@ -18580,12 +18595,13 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
18580
18595
  });
18581
18596
  }
18582
18597
  repairSessionAliases(canonical, aliases = []) {
18598
+ if (canonical && aliases.length === 0) return 0;
18583
18599
  const mapping = new Map(aliases.map((alias) => [alias, canonical]));
18584
18600
  let changed = 0;
18585
18601
  for (const table of ["nodes", "edges"]) {
18586
18602
  const predicates = canonical ? aliases.map(() => "instr(attrs_json, ?) > 0") : ["instr(attrs_json, 'codex:') > 0", "instr(attrs_json, 'discriminatorSource') > 0"];
18587
18603
  if (!predicates.length) continue;
18588
- const rows = this.db.prepare(`SELECT id, attrs_json FROM ${table} WHERE ${predicates.join(" OR ")}`).all(...canonical ? aliases : []);
18604
+ const rows = this.db.prepare(`SELECT id, attrs_json FROM ${table} WHERE (${SESSION_EVIDENCE_PREDICATE}) AND (${predicates.join(" OR ")})`).all(...canonical ? aliases.map((alias) => JSON.stringify(alias)) : []);
18589
18605
  const update = this.db.prepare(`UPDATE ${table} SET attrs_json = ?${table === "edges" ? ", type = CASE WHEN type = 'CONFIRMS' AND json_extract(?, '$.provisional') = 1 THEN 'INDICATES' ELSE type END" : ""} WHERE id = ?`);
18590
18606
  for (const row of rows) {
18591
18607
  const attrs = JSON.stringify(repairSessionEvidence(JSON.parse(row.attrs_json), mapping));
@@ -18811,6 +18827,25 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
18811
18827
  const rows = this.db.prepare("SELECT id FROM nodes WHERE label = ? AND valid_to IS NULL ORDER BY last_updated_at DESC LIMIT ?").all(label, Math.max(0, limit));
18812
18828
  return rows.map((r) => r.id);
18813
18829
  }
18830
+ recentOpenProblemCandidates(opts) {
18831
+ if (!Number.isSafeInteger(opts.limit) || opts.limit < 0) {
18832
+ throw new RangeError("candidate limit must be a non-negative safe integer");
18833
+ }
18834
+ return this.db.prepare(`SELECT id, description FROM nodes
18835
+ WHERE label = 'Problem' AND valid_to IS NULL
18836
+ AND json_extract(attrs_json, '$.resolvedAt') IS NULL
18837
+ AND json_type(attrs_json, '$.mergedInto') IS NULL
18838
+ AND (? IS NULL OR id != ?)
18839
+ AND (json_type(attrs_json, '$.workspaceId') IS NULL OR json_extract(attrs_json, '$.workspaceId') = ?)
18840
+ AND COALESCE(json_extract(attrs_json, '$.kind') = 'constraint', 0) = ?
18841
+ ORDER BY last_updated_at DESC, rowid ASC LIMIT ?`).all(
18842
+ opts.excludeId ?? null,
18843
+ opts.excludeId ?? null,
18844
+ opts.workspaceId,
18845
+ opts.constraint ? 1 : 0,
18846
+ opts.limit
18847
+ );
18848
+ }
18814
18849
  hasLiveNodes() {
18815
18850
  const r = this.db.prepare("SELECT EXISTS(SELECT 1 FROM nodes WHERE valid_to IS NULL) AS e").get();
18816
18851
  return r.e === 1;
@@ -18966,8 +19001,8 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
18966
19001
  const r2 = this.db.prepare(
18967
19002
  `SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
18968
19003
  MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s
18969
- FROM nodes INDEXED BY nodes_instance_fingerprint
18970
- WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause')`
19004
+ FROM nodes INDEXED BY nodes_instance_fingerprint_v2
19005
+ WHERE valid_to IS NULL AND label IN ('Problem','Vulnerability','Solution','RootCause')`
18971
19006
  ).get();
18972
19007
  return `${r2.n}:${r2.t ?? 0}:${r2.s ?? 0}`;
18973
19008
  }
@@ -20263,7 +20298,7 @@ function mergeContextEdge(store, from, to, type, ts) {
20263
20298
  function deriveContextFromAnchors(store, problemId, ts, pkgIndex) {
20264
20299
  const report = { languages: [], packages: [] };
20265
20300
  const p = store.getNode(problemId);
20266
- if (!p || p.label !== "Problem") return report;
20301
+ if (!p || p.label !== "Problem" && p.label !== "Vulnerability") return report;
20267
20302
  const files = store.outEdges(problemId, ["ANCHORED_AT"]).map((e) => store.getNode(e.to)).filter((n) => !!n && n.label === "File");
20268
20303
  if (files.length === 0) return report;
20269
20304
  const idx = pkgIndex ?? buildPackageIndex(store);
@@ -20901,6 +20936,81 @@ function ingestDesignProblem(store, flag, opts) {
20901
20936
  ...reopenedFromRegression ? { reopenedFromRegression } : {}
20902
20937
  };
20903
20938
  }
20939
+ function ingestWitnessedVulnerability(store, flag, opts) {
20940
+ const statement = flag.statement.trim();
20941
+ if (isPlaceholderStatement(statement)) return { id: "", created: false, corroborated: false, upgraded: false, rejected: "statement" };
20942
+ const target = flag.target?.trim().replace(/\\/g, "/").replace(/^\.\//, "") ?? "";
20943
+ const file2 = target ? resolveFileNode(store, target, opts.workspaceId) : null;
20944
+ const symbol2 = file2 && flag.line !== void 0 && flag.fileText !== void 0 ? resolveEnclosingSymbol(store, file2.id, flag.line, flag.fileText) : null;
20945
+ const identityTarget = symbol2 ? String(symbol2.attrs["logicalId"] ?? `${target}#${symbol2.attrs["qname"] ?? symbol2.description}`) : target;
20946
+ const compatibleTarget = (n) => !symbol2 || !n.attrs["identityTarget"] || n.attrs["identityTarget"] === target || n.attrs["identityTarget"] === identityTarget;
20947
+ const bound = flag.existingId ? store.getNode(flag.existingId) : null;
20948
+ if (flag.existingId && (!bound || bound.label !== "Vulnerability" || bound.attrs["workspaceId"] !== opts.workspaceId || target && bound.attrs["affectedTarget"] && bound.attrs["affectedTarget"] !== target || !compatibleTarget(bound))) {
20949
+ return { id: "", created: false, corroborated: false, upgraded: false, rejected: "binding-conflict" };
20950
+ }
20951
+ const statementKey = identityId({ kind: "DesignProblem", statement });
20952
+ const restated = bound ? null : store.findNodesByLabel("Vulnerability").find((n) => n.attrs["workspaceId"] === opts.workspaceId && n.attrs["affectedTarget"] === target && compatibleTarget(n) && [n.description, ...n.attrs["statementAliases"] ?? []].some((s) => identityId({ kind: "DesignProblem", statement: s }) === statementKey));
20953
+ const id = bound?.id ?? restated?.id ?? identityId({ kind: "WitnessedVulnerability", workspaceId: opts.workspaceId, target: identityTarget, statement });
20954
+ const existing = bound ?? store.getNode(id);
20955
+ const sources = existing?.attrs["sources"] ?? [];
20956
+ const corroborated = !!existing && !sources.includes(opts.source);
20957
+ const upgraded = !!existing && existing.attrs["assessment"] !== "asserted" && flag.assessment === "asserted";
20958
+ const assessment = existing?.attrs["assessment"] === "asserted" ? "asserted" : flag.assessment;
20959
+ if (!existing) {
20960
+ store.mergeNode(buildNode(store, id, "Vulnerability", statement, opts.ts, {
20961
+ source: "convo",
20962
+ sourceClass: "agent-observation",
20963
+ privateFinding: id,
20964
+ workspaceId: opts.workspaceId,
20965
+ affectedTarget: target,
20966
+ identityTarget,
20967
+ assessment,
20968
+ anchorDisposition: target ? "unresolved" : "absent",
20969
+ provisional: true,
20970
+ sources: [opts.source],
20971
+ corroborations: 0,
20972
+ ...opts.hostHarness ? { hostHarness: opts.hostHarness } : {}
20973
+ }));
20974
+ } else if (corroborated || upgraded || !existing.attrs["affectedTarget"] && target || symbol2 && existing.attrs["identityTarget"] !== identityTarget) {
20975
+ store.updateNode(id, {
20976
+ attrs: {
20977
+ ...existing.attrs,
20978
+ assessment,
20979
+ ...upgraded && existing.description !== statement ? { statementAliases: [...existing.attrs["statementAliases"] ?? [], existing.description].slice(-5) } : {},
20980
+ affectedTarget: existing.attrs["affectedTarget"] || target,
20981
+ ...symbol2 ? { identityTarget } : {},
20982
+ sources: corroborated ? [...sources, opts.source] : sources,
20983
+ corroborations: (Number(existing.attrs["corroborations"]) || 0) + (corroborated ? 1 : 0)
20984
+ },
20985
+ // Assessment strengthens on evidence, but does not certify exploitability.
20986
+ ...upgraded ? { description: statement } : {},
20987
+ ...corroborated ? { cumulativeHits: existing.cumulativeHits + 1 } : {},
20988
+ ...reinforcedSeqAttrs(store, "Vulnerability"),
20989
+ lastUpdatedAt: opts.ts
20990
+ });
20991
+ }
20992
+ return { id, created: !existing, corroborated, upgraded };
20993
+ }
20994
+ function recordVulnerabilityCause(store, findingId, causes, ts) {
20995
+ const finding = store.getNode(findingId);
20996
+ if (finding?.label !== "Vulnerability") return 0;
20997
+ let from = findingId;
20998
+ let count = 0;
20999
+ for (const description of causes) {
21000
+ if (isPlaceholderStatement(description)) continue;
21001
+ const id = genericCanonicalId("dcause", { findingId, description: description.trim() });
21002
+ if (!store.getNode(id)) store.mergeNode(buildNode(store, id, "RootCause", description.trim(), ts, {
21003
+ source: "convo",
21004
+ privateFinding: findingId,
21005
+ sources: finding.attrs["sources"],
21006
+ assessment: finding.attrs["assessment"]
21007
+ }));
21008
+ mergeEdge(store, from, id, "CAUSED_BY", ts);
21009
+ from = id;
21010
+ count++;
21011
+ }
21012
+ return count;
21013
+ }
20904
21014
  function canonicalizePatternText(name2) {
20905
21015
  const stripped = name2.replace(/^\s*pattern(?:\s*[×x]\s*\d+)?\s*(?:\(exemplar\))?\s*[:—–-]\s*/i, "").replace(/\s+/g, " ").trim();
20906
21016
  return stripped.length > 0 ? stripped : name2.replace(/\s+/g, " ").trim();
@@ -21211,6 +21321,7 @@ function findReusableSolution(store, fixText) {
21211
21321
  const key = solutionTextKey(fixText);
21212
21322
  if (!key) return null;
21213
21323
  for (const s of store.findNodesByLabel("Solution")) {
21324
+ if (s.attrs["privateFinding"]) continue;
21214
21325
  if (s.validTo != null) continue;
21215
21326
  if (isAutoMinted(s)) continue;
21216
21327
  if (solutionTextKey(s.description ?? "") === key) return s;
@@ -21235,7 +21346,7 @@ function priorsForFile(store, relPath) {
21235
21346
  for (const e of store.inEdges(file2.id, ["ANCHORED_AT"])) {
21236
21347
  const n = store.getNode(e.from);
21237
21348
  if (!n) continue;
21238
- if (n.label === "Problem") {
21349
+ if (n.label === "Problem" || n.label === "Vulnerability") {
21239
21350
  if (seenProblem.has(n.id)) continue;
21240
21351
  seenProblem.add(n.id);
21241
21352
  if (!n.attrs["resolvedAt"]) {
@@ -21305,7 +21416,8 @@ function solutionForProblem(store, problemId) {
21305
21416
  }
21306
21417
  function anchorProblemToWorkingFile(store, problemId, relPath, workspaceId2, ts, provenance = "edited", opts = {}) {
21307
21418
  const p = store.getNode(problemId);
21308
- if (!p || p.label !== "Problem") return false;
21419
+ if (!p || p.label !== "Problem" && p.label !== "Vulnerability") return false;
21420
+ if (p.label === "Vulnerability" && provenance !== "witnessed") return false;
21309
21421
  if (!isAnchorableCodePath(relPath)) return false;
21310
21422
  const file2 = resolveFileNode(store, relPath, workspaceId2);
21311
21423
  if (!file2) return false;
@@ -21484,18 +21596,22 @@ function resolveDesignProblemById(store, id, fixNote, t) {
21484
21596
  }
21485
21597
  function closeDesignProblem(store, problemId, fixNote, t) {
21486
21598
  const p = store.getNode(problemId);
21487
- if (!p || p.label !== "Problem") return false;
21599
+ if (!p || p.label !== "Problem" && p.label !== "Vulnerability") return false;
21488
21600
  if (p.attrs["resolvedAs"]) return false;
21489
21601
  if (fixNote?.trim() && !isFragmentFixNote(fixNote)) {
21490
21602
  const sols = store.outEdges(problemId, ["SOLVED_BY"]).map((e) => store.getNode(e.to)).filter((n) => n !== null);
21491
21603
  if (sols.every((s) => isAutoMinted(s))) {
21492
- const reuse = findReusableSolution(store, fixNote);
21604
+ const reuse = p.label === "Vulnerability" ? null : findReusableSolution(store, fixNote);
21493
21605
  if (reuse) {
21494
21606
  mergeEdge(store, problemId, reuse.id, "SOLVED_BY", t);
21495
21607
  } else {
21496
21608
  const solId = `dfix_${digest({ problemId, fix: fixNote })}`.slice(0, 56);
21497
21609
  store.mergeNode(
21498
- buildNode(store, solId, "Solution", fixNote.trim(), t, { source: "convo", provisional: false })
21610
+ buildNode(store, solId, "Solution", fixNote.trim(), t, {
21611
+ source: "convo",
21612
+ provisional: false,
21613
+ ...p.label === "Vulnerability" ? { privateFinding: problemId, sources: p.attrs["sources"] } : {}
21614
+ })
21499
21615
  );
21500
21616
  mergeEdge(store, problemId, solId, "SOLVED_BY", t);
21501
21617
  }
@@ -21509,6 +21625,7 @@ function closeDesignProblem(store, problemId, fixNote, t) {
21509
21625
  // mints no Solution, and a legacy placeholder beside it reads as
21510
21626
  // all-heuristic either way.
21511
21627
  attrs: { ...p.attrs, provisional: false, resolvedAt: t, resolutionWitnessed: true },
21628
+ ...reinforcedSeqAttrs(store, p.label),
21512
21629
  lastUpdatedAt: t
21513
21630
  });
21514
21631
  return true;
@@ -23860,7 +23977,7 @@ function detectLocalCommunities(store, opts = {}) {
23860
23977
  const ids = /* @__PURE__ */ new Set();
23861
23978
  for (const label of JOINT_COMMUNITY_LABELS) {
23862
23979
  for (const n of store.findNodesByLabel(label)) {
23863
- if (label === "Problem" && n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) continue;
23980
+ if (n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) continue;
23864
23981
  ids.add(n.id);
23865
23982
  }
23866
23983
  }
@@ -23997,6 +24114,7 @@ var init_community2 = __esm({
23997
24114
  init_src4();
23998
24115
  JOINT_COMMUNITY_LABELS = [
23999
24116
  "Problem",
24117
+ "Vulnerability",
24000
24118
  "Solution",
24001
24119
  "RootCause",
24002
24120
  "Pattern"
@@ -24041,10 +24159,12 @@ function markFoldCandidate(store, problemId, targetId, info2) {
24041
24159
  }
24042
24160
  function findWorkspaceFoldCandidate(store, workspaceId2, statement, kind, opts) {
24043
24161
  const stmtVec = embed(statement);
24044
- const wantConstraint = kind === "constraint";
24045
- const open2 = store.findNodesByLabel("Problem").filter(
24046
- (p) => p.attrs["resolvedAt"] == null && p.attrs["mergedInto"] === void 0 && p.id !== opts?.excludeId && (p.attrs["workspaceId"] === void 0 || p.attrs["workspaceId"] === workspaceId2) && isConstraintProblem(p) === wantConstraint
24047
- ).sort((a, b) => (b.lastUpdatedAt ?? b.createdAt) - (a.lastUpdatedAt ?? a.createdAt)).slice(0, opts?.scanCap ?? WORKSPACE_SCAN_CAP);
24162
+ const open2 = store.recentOpenProblemCandidates({
24163
+ workspaceId: workspaceId2,
24164
+ constraint: kind === "constraint",
24165
+ limit: opts?.scanCap ?? WORKSPACE_SCAN_CAP,
24166
+ ...opts?.excludeId !== void 0 ? { excludeId: opts.excludeId } : {}
24167
+ });
24048
24168
  let best = null;
24049
24169
  for (const p of open2) {
24050
24170
  const c = cosine(stmtVec, embed(p.description));
@@ -24858,6 +24978,7 @@ __export(src_exports2, {
24858
24978
  induceAbstractions: () => induceAbstractions,
24859
24979
  induceTriage: () => induceTriage,
24860
24980
  ingestDesignProblem: () => ingestDesignProblem,
24981
+ ingestWitnessedVulnerability: () => ingestWitnessedVulnerability,
24861
24982
  isConstraintProblem: () => isConstraintProblem,
24862
24983
  isFragmentFixNote: () => isFragmentFixNote,
24863
24984
  isPlaceholderStatement: () => isPlaceholderStatement,
@@ -24905,6 +25026,7 @@ __export(src_exports2, {
24905
25026
  recordMisreadPrior: () => recordMisreadPrior,
24906
25027
  recordRenderLedger: () => recordRenderLedger,
24907
25028
  recordTriageObservation: () => recordTriageObservation,
25029
+ recordVulnerabilityCause: () => recordVulnerabilityCause,
24908
25030
  reinforceSameAnchorProblem: () => reinforceSameAnchorProblem,
24909
25031
  relateClaims: () => relateClaims,
24910
25032
  reopenAutoClosedProblems: () => reopenAutoClosedProblems,
@@ -24974,7 +25096,7 @@ function isPassiveInjectable(node2, atCommit) {
24974
25096
  return false;
24975
25097
  }
24976
25098
  function buildSnapshot(opts) {
24977
- const problems = opts.store.findNodesByLabel("Problem").filter((p) => p.attrs["mergedInto"] === void 0 && historyReadPolicy(p, { mode: "passive", atCommit: opts.atCommit }).allow);
25099
+ const problems = [...opts.store.findNodesByLabel("Problem"), ...opts.store.findNodesByLabel("Vulnerability")].filter((p) => p.attrs["mergedInto"] === void 0 && historyReadPolicy(p, { mode: "passive", atCommit: opts.atCommit }).allow);
24978
25100
  const stillOpen = problems.filter((p) => p.attrs["resolvedAt"] == null);
24979
25101
  const allProblems = stillOpen.filter((p) => !isConstraintProblem(p));
24980
25102
  allProblems.sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt);
@@ -25129,11 +25251,11 @@ function sliceForFile(store, relPath, atCommit) {
25129
25251
  const priors = fp ? {
25130
25252
  openProblems: fp.openProblems.slice(0, 3).map((p) => ({
25131
25253
  id: p.id,
25132
- description: p.description,
25254
+ description: p.label === "Vulnerability" ? `Vulnerability (${p.attrs["assessment"] ?? "unassessed"}): ${p.description}` : p.description,
25133
25255
  ...typeof p.attrs["fixCandidateFile"] === "string" ? { fixCandidateFile: p.attrs["fixCandidateFile"] } : {}
25134
25256
  })),
25135
25257
  constraints: fp.constraints.slice(0, 2).map((c) => ({ id: c.id, description: c.description })),
25136
- resolvedProblems: fp.resolvedProblems.slice(0, 3).map((p) => ({ id: p.id, description: p.description })),
25258
+ resolvedProblems: fp.resolvedProblems.slice(0, 3).map((p) => ({ id: p.id, description: p.label === "Vulnerability" ? `Vulnerability (${p.attrs["assessment"] ?? "unassessed"}): ${p.description}` : p.description })),
25137
25259
  related: fp.related.slice(0, 4).map((n) => ({ id: n.id, label: n.label, description: n.description })),
25138
25260
  solutionsByProblem: Object.fromEntries(
25139
25261
  [...fp.openProblems.slice(0, 3), ...fp.resolvedProblems.slice(0, 3)].map((p) => [
@@ -25251,7 +25373,8 @@ function renderSnapshot(s) {
25251
25373
  if (s.recentProblems.length > 0) {
25252
25374
  lines.push("_Open \u2014 recently observed here, derived from your work (a recency slice, not an inventory \u2014 burst an id for its full neighborhood):_");
25253
25375
  for (const r of s.recentProblems) {
25254
- lines.push(`- **${r.node.description}** \u2014 \`${r.node.id}\`${tagOf(r.node)}`);
25376
+ const badge = r.node.label === "Vulnerability" ? `Vulnerability (${r.node.attrs["assessment"] ?? "unassessed"}): ` : "";
25377
+ lines.push(`- **${badge}${r.node.description}** \u2014 \`${r.node.id}\`${tagOf(r.node)}`);
25255
25378
  const hits = r.node.cumulativeHits ?? 0;
25256
25379
  const surprise = (r.node.cumulativeSurprise ?? 0).toFixed(2);
25257
25380
  lines.push(` - observed ${hits}\xD7 \xB7 cumulativeSurprise ${surprise}`);
@@ -25292,7 +25415,8 @@ function renderSnapshot(s) {
25292
25415
  if (s.recentResolved.length > 0) {
25293
25416
  lines.push("_Recently resolved \u2014 solved here; jumping-off points, not live defects:_");
25294
25417
  for (const r of s.recentResolved) {
25295
- lines.push(`- \u2713 **${r.node.description}** \u2014 \`${r.node.id}\`${tagOf(r.node)}`);
25418
+ const badge = r.node.label === "Vulnerability" ? `Vulnerability (${r.node.attrs["assessment"] ?? "unassessed"}): ` : "";
25419
+ lines.push(`- \u2713 **${badge}${r.node.description}** \u2014 \`${r.node.id}\`${tagOf(r.node)}`);
25296
25420
  if (r.solution) {
25297
25421
  lines.push(` - solved: ${r.solution.description.slice(0, 160)} \u2014 \`${r.solution.id}\`${tagOf(r.solution)}`);
25298
25422
  }
@@ -27152,8 +27276,8 @@ var init_config = __esm({
27152
27276
  "use strict";
27153
27277
  init_paths();
27154
27278
  LEGACY_CLOUD_URL = "https://inerrata-gateway.onrender.com";
27155
- DEFAULT_CLOUD_URL = true ? "https://inerrata.dev" : DEV_CLOUD_URL;
27156
- DEFAULT_UPDATE_CHANNEL = true ? "latest" : "dev";
27279
+ DEFAULT_CLOUD_URL = true ? "https://dev.inerrata.dev" : DEV_CLOUD_URL;
27280
+ DEFAULT_UPDATE_CHANNEL = false ? "latest" : "dev";
27157
27281
  }
27158
27282
  });
27159
27283
 
@@ -31713,7 +31837,7 @@ var init_src8 = __esm({
31713
31837
 
31714
31838
  // src/agent-signals.ts
31715
31839
  function buildFileRecallInstruction(referent = "\u2026") {
31716
- return `Touching one? paste its token: ${TAG_EXAMPLE.fix(referent)} resolved \xB7 ${TAG_EXAMPLE.prior(referent)} leaned-on \xB7 ${TAG_EXAMPLE.problem()} new. We harvest it.`;
31840
+ return `Touching one? paste its token: ${TAG_EXAMPLE.fix(referent)} resolved \xB7 ${TAG_EXAMPLE.prior(referent)} leaned-on \xB7 ${TAG_EXAMPLE.problem()} new \xB7 ${TAG_EXAMPLE.vulnerability()} security. We harvest it.`;
31717
31841
  }
31718
31842
  function triageBullet(ref) {
31719
31843
  return [
@@ -31749,15 +31873,15 @@ function attemptBullet() {
31749
31873
  ].join("\n");
31750
31874
  }
31751
31875
  function buildAgentInstruction(opts = {}) {
31752
- const signals = opts.signals ?? ["prior", "problem", "domain", "fix", "constraint", "triage", "attempt", "link"];
31876
+ const signals = opts.signals ?? ["prior", "problem", "vulnerability", "domain", "fix", "constraint", "triage", "attempt", "link"];
31753
31877
  const ref = opts.referent ?? "its-handle";
31754
31878
  const token = (s) => s === "problem" ? TAG_EXAMPLE.problem() : s === "constraint" ? TAG_EXAMPLE.constraint() : s === "attempt" ? TAG_EXAMPLE.tried() : s === "domain" ? TAG_EXAMPLE.domain() : s === "link" ? TAG_EXAMPLE.pattern() : s === "verdict" ? TAG_EXAMPLE.confirm(ref) : TAG_EXAMPLE[s](ref);
31755
- const head2 = `We tag the priors we show you with a short handle like [${opts.handleExample ?? "chokidar-glob"}]. Untagged prose is silently lost. Put each tag inline in prose (no fences or backticks). Requested errata-* answer blocks are the exception: keep their fences in your turn-final reply. Tag everything, we filter downstream:`;
31756
- const tail = "Mid-turn text (between tool calls) can be dropped by the harness \u2014 RESTATE every tag in your turn-final message; the final message is what reliably survives \u2014 EXCEPT one already shown above with an id: that is captured, so cite `[its-handle]` rather than retyping it (drift mints a duplicate).";
31879
+ const head2 = "Tag inline (no fences or backticks). Untagged findings are silently lost. Cite short handles; tag everything, we filter downstream. Requested errata-* answer blocks are the exception: keep their fences in your turn-final reply:";
31880
+ const tail = "Restate mid-turn tags in your final reply; the harness can drop mid-turn text. EXCEPT one already shown with an id: cite [its-handle] instead of retyping (drift mints a duplicate).";
31757
31881
  return [
31758
31882
  head2,
31759
31883
  ...signals.map(
31760
- (s) => s === "triage" ? triageBullet(ref) : s === "attempt" ? attemptBullet() : s === "link" ? linkBullet(ref) : s === "verdict" ? verdictBullet(ref) : ` \u2022 ${GLOSS[s]} \u2192 ${token(s)}`
31884
+ (s) => s === "triage" ? triageBullet(ref) : s === "vulnerability" ? ` \u2022 ${GLOSS.vulnerability} \u2192 ${TAG_EXAMPLE.vulnerability()}. Uncertain? ${TAG_EXAMPLE.suspectedVulnerability()}. Code review counts, even if immediately fixed. No CVE/exploit needed. For fixes: ${TAG_EXAMPLE.vulnerabilityThread()} ${TAG_EXAMPLE.fixThread("v")}.` : s === "attempt" ? attemptBullet() : s === "link" ? linkBullet(ref) : s === "verdict" ? verdictBullet(ref) : ` \u2022 ${GLOSS[s]} \u2192 ${token(s)}`
31761
31885
  ),
31762
31886
  tail
31763
31887
  ].join("\n");
@@ -31769,6 +31893,9 @@ var init_agent_signals = __esm({
31769
31893
  TAG_EXAMPLE = {
31770
31894
  prior: (ref) => `([${ref}])`,
31771
31895
  problem: () => `[!one line on what's wrong]`,
31896
+ vulnerability: () => `[!vuln: who can do what; observed basis @ path:line]`,
31897
+ suspectedVulnerability: () => `[?vuln: consequence; missing check]`,
31898
+ vulnerabilityThread: () => `[!#v vuln: finding here]`,
31772
31899
  fix: (ref) => `(fix:[${ref}])`,
31773
31900
  // CONSTRAINT — the native capture path for a DIFFUSE problem (a constraint/tension/
31774
31901
  // conflict that shaped a decision). Free text, attaches to the decision sentence the
@@ -31789,7 +31916,7 @@ var init_agent_signals = __esm({
31789
31916
  // tags never need one — the id is for diagnoses that unfold over distance.
31790
31917
  problemThread: () => `[!#slug one line on what's wrong]`,
31791
31918
  causeThread: () => `(cause:#slug the mechanism)`,
31792
- fixThread: () => `(fix:#slug what you changed)`,
31919
+ fixThread: (thread = "slug") => `(fix:#${thread} what you changed)`,
31793
31920
  // ATTEMPT arc — what's being tried and what didn't work. A failed attempt is a
31794
31921
  // witnessed NEGATIVE result: it saves the next agent the same dead end.
31795
31922
  tried: () => `(tried: the approach you're attempting)`,
@@ -31831,8 +31958,9 @@ var init_agent_signals = __esm({
31831
31958
  // problems, and narrated them as progress (a pick-best-before-scoring bug lost).
31832
31959
  // Every example here was diagnosis-framed, so nothing contradicted that sorting.
31833
31960
  // Kept to 29 chars: the instruction sits at the 2600 passive-budget ceiling.
31834
- problem: "a problem you notice or report \u2014 any bug, finding, or thing wrong, even in code you just wrote; about specific code? name the file: [!what's wrong @ src/file.ts:42]",
31835
- fix: "your edit fixed a PROBLEM we showed you; for one you flagged yourself: (fix: what you changed) \u2014 a fix resolves a stated problem, completing routine work is not a fix",
31961
+ problem: "any bug or finding, even in code you just wrote; name affected code: [!what's wrong @ path:42]",
31962
+ vulnerability: "unauthorized actions, data exposure, or execution/disruption an attacker can trigger",
31963
+ fix: "you fixed a shown finding; for your own non-security finding: (fix: what changed) \u2014 a fix resolves a stated problem, completing routine work is not a fix",
31836
31964
  constraint: "a design decision forced by conflicting requirements or constraints \u2014 tag the tension, especially when your fix makes it 'only look' resolved",
31837
31965
  triage: "you diagnosed a cause \u2014 fold the chain into the flag with <- (caused-by), deepest sure mechanism rightmost",
31838
31966
  attempt: "attempting an approach / an attempt failed \u2014 a failed attempt is knowledge, it rules out a path",
@@ -31854,7 +31982,7 @@ var init_agent_signals = __esm({
31854
31982
  // Sim note (2026-08-02 shrink): domain emission held 4/4 with just the trigger
31855
31983
  // clause; the pattern convergence hook now rides here too (it fell 0/4 when it
31856
31984
  // only appeared in the link bullet's tail).
31857
- domain: "a problem with no single file \u2014 name the area it's about (Title Case). Also name the general shape it instantiates: (pattern: the shape) \u2014 needs no code anchor, and two agents naming the same shape converge on one node",
31985
+ domain: "no single file? name the area (Title Case), and (pattern: the shape). No code anchor needed; two agents naming the same shape converge on one node",
31858
31986
  link: "the problem you flagged is an instance of priors/abstractions you can name \u2014 (instance:[h1],[h2],\u2026) / (pattern: \u2026) / (aids:[h]); rendered whole via linkBullet",
31859
31987
  verdict: "you can judge a prior we SHOWED you \u2014 confirm it if it held, refute it (with why) if it didn't; rendered whole via verdictBullet"
31860
31988
  };
@@ -32095,7 +32223,7 @@ function readContextCard(root, hash2) {
32095
32223
  function openProblems(store, now = Date.now()) {
32096
32224
  const hit = openProblemsCache.get(store);
32097
32225
  if (hit && now - hit.at < OPEN_PROBLEMS_TTL_MS) return hit.nodes;
32098
- const nodes = store.findNodesByLabel("Problem").filter((p) => live(p) && !p.attrs["resolvedAt"] && p.attrs["mergedInto"] === void 0 && !isConstraintProblem(p));
32226
+ const nodes = [...store.findNodesByLabel("Problem"), ...store.findNodesByLabel("Vulnerability")].filter((p) => live(p) && !p.attrs["resolvedAt"] && p.attrs["mergedInto"] === void 0 && !isConstraintProblem(p));
32099
32227
  openProblemsCache.set(store, { at: now, nodes });
32100
32228
  return nodes;
32101
32229
  }
@@ -43482,47 +43610,9 @@ var init_mcp = __esm({
43482
43610
  init_reconcile();
43483
43611
  init_agent_signals();
43484
43612
  INSTRUCTIONS = [
43485
- // KEEP THIS UNDER THE CLIENT'S ~2 KB instruction-injection budget (the client
43486
- // truncates `instructions` on connect, ~2008 chars observed). Capture no longer
43487
- // depends on a standing emit-instruction block: the daemon derives problems/fixes
43488
- // from your WORK deterministically, and the one thing it asks of the agent — a
43489
- // one-line root cause at a fix — rides INLINE on the next tool response (the ⚡
43490
- // errata line; see the dispatch's enrichment piggyback) instead of a fenced
43491
- // protocol the agent has to remember. So this stays short: tool-routing steering
43492
- // plus a single line about the inline nudge. Per-tool routing lives in each tool's
43493
- // own `description` (delivered via tools/list). Don't re-add a tool table here.
43494
- "errata is a LIVE, bi-temporal code graph of this workspace, kept current by a",
43495
- "file watcher \u2014 a SEPARATE DIAGNOSTIC AXIS to run ALONGSIDE grep/glob/read, not",
43496
- "instead of them. For STRUCTURAL questions (callers of callers, blast-radius,",
43497
- "rename lineage) prefer it: faster, more complete, and it reaches relationships",
43498
- "grep can't. Each tool's description says when to reach for it. Fall back to",
43499
- 'grep/read when a tool returns found:false (ESPECIALLY reason:"unresolved" \u2014 the',
43500
- "name was not recognized, NOT an empty answer; an empty list like callers:[] is a",
43501
- 'confident "none" \u2014 trust it), when you need EXACT bytes before a high-stakes',
43502
- "edit, or for an unindexed file/language. Always confirm exact bytes with a read",
43503
- "before editing.",
43504
- "",
43505
- "errata is a cloud knowledge substrate \u2014 AND ALSO this workspace's code graph.",
43506
- "The problems you hit and the fixes you make become shared knowledge other agents",
43507
- "recall. That capture is automatic: derived from your edits/commands/commits \u2014 no",
43508
- "emit needed. Just after you resolve something a tool result may carry a one-line",
43509
- "\u26A1 errata note asking the ROOT CAUSE; answer via errata.annotate_resolution when",
43510
- "you know it, skip it otherwise.",
43511
- "",
43512
- "Two things it CAN'T derive \u2014 only you witness them, and this is a CAPTURE",
43513
- "PROTOCOL, not optional formatting: a problem or fix you state only in prose, a",
43514
- "list, or your own numbering is SILENTLY LOST \u2014 the tag is the ONLY thing recorded.",
43515
- "So the moment you state one, wrap it inline (no fence, no extra call):",
43516
- // Signal tokens come from the single source (agent-signals.ts) so this always-on
43517
- // block can't teach a grammar the parser rejects. Referent is "its-id" here — the
43518
- // raw MCP channel has no primed handles, so the agent cites a tool-result id.
43519
- ` \u2022 ${TAG_EXAMPLE.problem()} \u2014 EVERY problem you notice or report ([?\u2026] = TODO)`,
43520
- ` \u2022 ${TAG_EXAMPLE.fix("its-id")} \u2014 your edit fixed a problem errata showed you`,
43521
- "Tag every one you state \u2014 we filter downstream, so don't self-censor or batch.",
43522
- "",
43523
- "Graph-as-files: the graph is ALSO a virtual FS \u2014 Read (or `cat`)",
43524
- ".errata/g/<verb>/<seed> to run a query as a file read (e.g. .errata/g/burst/",
43525
- "<symbol>, /why/<sym>, /trace/<a>..<b>). Read .errata/g/index for the vocabulary."
43613
+ "errata is a live code and knowledge graph, a SEPARATE DIAGNOSTIC AXIS alongside grep/read for callers, blast radius, rename lineage and prior fixes. Fall back to read/search on found:false (unresolved is not an empty answer), or for unindexed code. Confirm exact bytes before editing.",
43614
+ buildAgentInstruction({ signals: ["prior", "problem", "vulnerability", "fix"], referent: "its-id" }),
43615
+ "Read .errata/g/index for graph-as-files query paths. A tool result may ask for a root cause; answer via errata.annotate_resolution when known."
43526
43616
  ].join("\n");
43527
43617
  AUDIT_RUN_THRESHOLD = 5;
43528
43618
  AUDIT_NUDGE_COOLDOWN = 12;
@@ -44114,7 +44204,7 @@ var init_mcp = __esm({
44114
44204
  },
44115
44205
  {
44116
44206
  name: "errata.problems",
44117
- description: "List the workspace's Problem backlog \u2014 the triage view that search/why/impact can't give because they need a seed. status: 'open' (default) | 'resolved' (a real fix) | 'retracted' (false alarms \u2014 false_positive/dissolution) | 'constraint' (design TENSIONS captured via `(constraint: \u2026)` \u2014 standing context, not open work, so they're excluded from 'open') | 'all'. Also returns a `retracted` breakdown so the false-positive rate stays visible.",
44207
+ description: "List the workspace's Problem and Vulnerability backlog \u2014 the triage view that search/why/impact can't give because they need a seed. status: 'open' (default) | 'resolved' (a real fix) | 'retracted' (false alarms \u2014 false_positive/dissolution) | 'constraint' (design TENSIONS captured via `(constraint: \u2026)` \u2014 standing context, not open work, so they're excluded from 'open') | 'all'. Also returns a `retracted` breakdown so the false-positive rate stays visible.",
44118
44208
  inputSchema: {
44119
44209
  type: "object",
44120
44210
  properties: {
@@ -44126,13 +44216,15 @@ var init_mcp = __esm({
44126
44216
  const want = String(args2["status"] ?? "open");
44127
44217
  const limit = Math.max(1, Math.min(200, Number(args2["limit"] ?? 50)));
44128
44218
  const RETRACTIONS = /* @__PURE__ */ new Set(["false_positive", "invalid", "duplicate"]);
44129
- const rows = store.findAllVersionsByLabel("Problem").map((p) => {
44219
+ const rows = [...store.findAllVersionsByLabel("Problem"), ...store.findAllVersionsByLabel("Vulnerability")].map((p) => {
44130
44220
  const ra = p.attrs["resolvedAs"];
44131
44221
  const status = ra && RETRACTIONS.has(ra) ? ra : p.attrs["resolvedAt"] != null ? "resolved" : isConstraintProblem(p) ? "constraint" : "open";
44132
44222
  const anchorEdge = store.outEdges(p.id, ["ANCHORED_AT"])[0];
44133
44223
  const anchor = anchorEdge ? store.getNode(anchorEdge.to)?.description : void 0;
44134
44224
  return {
44135
44225
  id: p.id,
44226
+ label: p.label,
44227
+ ...p.label === "Vulnerability" ? { assessment: p.attrs["assessment"] } : {},
44136
44228
  problem: p.description,
44137
44229
  status,
44138
44230
  createdAt: p.createdAt,
@@ -52697,7 +52789,8 @@ function errorRecallTerms(errorText) {
52697
52789
  }
52698
52790
  function recallLine(n, token) {
52699
52791
  const desc = (n.description ?? "").replace(/\s+/g, " ").slice(0, 120);
52700
- return `- [${n.label}] ${desc} \u2192 ${token}`;
52792
+ const label = n.label === "Vulnerability" ? `Vulnerability; ${n.attrs["assessment"] ?? "unassessed"}` : n.label;
52793
+ return `- [${label}] ${desc} \u2192 ${token}`;
52701
52794
  }
52702
52795
  function recallForFile(store, relPath) {
52703
52796
  return fileRecall(store, relPath)?.context ?? null;
@@ -52718,7 +52811,7 @@ function fileRecall(store, relPath) {
52718
52811
  const shownRelated = priors.related.slice(0, 4);
52719
52812
  for (const p of shownProblems) {
52720
52813
  const hasCause = store.outEdges(p.id, ["CAUSED_BY"]).some((e) => store.getNode(e.to)?.label === "RootCause");
52721
- const causeCue = hasCause ? "" : ` \xB7 ${TAG_EXAMPLE.triageMint()} to diagnose its cause`;
52814
+ const causeCue = hasCause ? "" : ` \xB7 ${p.label === "Vulnerability" ? `(cause:#${p.id} the mechanism)` : TAG_EXAMPLE.triageMint()} to diagnose its cause`;
52722
52815
  lines.push(
52723
52816
  recallLine(p, `${TAG_EXAMPLE.fix(p.id)} if you resolved it \xB7 ${TAG_EXAMPLE.prior(p.id)} to cite${causeCue}`)
52724
52817
  );
@@ -75988,7 +76081,7 @@ init_src5();
75988
76081
  import { readFileSync as readFileSync17, writeFileSync as writeFileSync13 } from "node:fs";
75989
76082
  var NEG = /n['']t|\b(?:not|never|no|none|neither|nor|unrelated|irrelevant|would|might|could|if)\b/i;
75990
76083
  var CITE_GROUP_RE = /\(((?:[^()\n]|\([^()\n]*\)){1,200})\)/g;
75991
- var HANDLE_RE = /\[([a-z0-9][\w-]{0,80})\]|((?:pat|sol|drc|dcause|dfix|dprob|prob|claim|err)_[0-9a-f]{6,})/gi;
76084
+ var HANDLE_RE = /\[([a-z0-9][\w-]{0,80})\]|((?:pat|sol|drc|dcause|dfix|dprob|dvuln|prob|claim|err)_[0-9a-f]{6,})/gi;
75992
76085
  var PLACEHOLDER_HANDLES = /* @__PURE__ */ new Set([
75993
76086
  "handle",
75994
76087
  "its-handle",
@@ -76003,7 +76096,7 @@ var PLACEHOLDER_HANDLES = /* @__PURE__ */ new Set([
76003
76096
  "b"
76004
76097
  ]);
76005
76098
  var isPlaceholderHandle = (h) => PLACEHOLDER_HANDLES.has(h.toLowerCase());
76006
- var FLAG_BODY_HANDLE_RE = /^(?:[a-z0-9]+(?:-[a-z0-9]+){2,}-(?=[0-9a-f]{0,3}\d)[0-9a-f]{4}|(?:pat|sol|drc|dcause|dfix|dprob|prob|claim|err)_[0-9a-f]{6,})$/i;
76099
+ var FLAG_BODY_HANDLE_RE = /^(?:[a-z0-9]+(?:-[a-z0-9]+){2,}-(?=[0-9a-f]{0,3}\d)[0-9a-f]{4}|(?:pat|sol|drc|dcause|dfix|dprob|dvuln|prob|claim|err)_[0-9a-f]{6,})$/i;
76007
76100
  var FIX_PREFIX = /^\s*fix:(?:#([a-z][\w-]{0,63}))?\s*/i;
76008
76101
  var CONSTRAINT_RE = /\(\s*constraint:\s*([^()\n]{8,}?)\s*\)/gi;
76009
76102
  var CAUSE_PREFIX = /^\s*cause:(?:#([a-z][\w-]{0,63}))?\s*/i;
@@ -76028,7 +76121,7 @@ var DOMAIN_RE = /\(\s*domain:\s*([^()\n]{3,}?)\s*\)/gi;
76028
76121
  var PACKAGE_RE = /\(\s*package:\s*([^()\n]{2,}?)\s*\)/gi;
76029
76122
  var COMPONENT_RE = /\(\s*component:\s*([^()\n]{2,}?)\s*\)/gi;
76030
76123
  var CAUSE_TEXT_MIN = 8;
76031
- var FLAG_RE = /\[([!?])(?:#([a-z][\w-]{0,63})\s+)?\s*((?:\[[^\]\n]*\]|[^\]\n]){8,}?)\s*(?:@\s*([^\s\]]+?)(?::(\d+))?\s*)?\]/g;
76124
+ var FLAG_RE = /\[([!?])(?:#([a-z][\w-]{0,63})\s+)?\s*((?:\[[^\]\n]*\]|[^\]\n])*?)\s*(?:@\s*([^\s\]]+?)(?::(\d+))?\s*)?\]/g;
76032
76125
  var CONSTRAINT_MIN = 8;
76033
76126
  function clauseBefore(sentence, idx) {
76034
76127
  const win = sentence.slice(Math.max(0, idx - 80), idx);
@@ -76172,6 +76265,7 @@ function cleanEmittedTags(tags) {
76172
76265
  }
76173
76266
  function emptyTagDispositions() {
76174
76267
  return {
76268
+ invalidVulnerability: 0,
76175
76269
  oversizeSegments: 0,
76176
76270
  codeSpanNeutralized: 0,
76177
76271
  refuteTooShort: 0,
@@ -76184,6 +76278,7 @@ function emptyTagDispositions() {
76184
76278
  };
76185
76279
  }
76186
76280
  function addTagDispositions(a, b) {
76281
+ a.invalidVulnerability += b.invalidVulnerability;
76187
76282
  a.oversizeSegments += b.oversizeSegments;
76188
76283
  a.codeSpanNeutralized += b.codeSpanNeutralized;
76189
76284
  a.refuteTooShort += b.refuteTooShort;
@@ -76197,12 +76292,31 @@ function addTagDispositions(a, b) {
76197
76292
  function parseInlineTags(text) {
76198
76293
  return parseInlineTagsWithDispositions(text).tags;
76199
76294
  }
76295
+ function* captureSentences(text) {
76296
+ for (const line of text.split(/\n+/)) {
76297
+ if (line.length > 2e4) {
76298
+ yield line;
76299
+ continue;
76300
+ }
76301
+ const flags2 = [...line.matchAll(new RegExp(FLAG_RE.source, FLAG_RE.flags))].map((m) => ({ start: m.index, end: m.index + m[0].length }));
76302
+ let flag = 0;
76303
+ let start2 = 0;
76304
+ for (const boundary of line.matchAll(/(?<=[.!?])\s+(?!\(fix:)/g)) {
76305
+ const index = boundary.index;
76306
+ while (flags2[flag] && flags2[flag].end <= index) flag++;
76307
+ if (flags2[flag] && flags2[flag].start < index) continue;
76308
+ yield line.slice(start2, index);
76309
+ start2 = index + boundary[0].length;
76310
+ }
76311
+ yield line.slice(start2);
76312
+ }
76313
+ }
76200
76314
  function parseInlineTagsWithDispositions(text) {
76201
76315
  const out2 = [];
76202
76316
  const dispositions = emptyTagDispositions();
76203
76317
  const deFenced = text.replace(/```[\s\S]*?```/g, " ");
76204
76318
  let seqNo = -1;
76205
- for (const raw2 of deFenced.split(/(?<=[.!?])\s+(?!\(fix:)|\n+/)) {
76319
+ for (const raw2 of captureSentences(deFenced)) {
76206
76320
  seqNo++;
76207
76321
  if (raw2.length > 2e4) {
76208
76322
  dispositions.oversizeSegments++;
@@ -76428,6 +76542,29 @@ function parseInlineTagsWithDispositions(text) {
76428
76542
  while ((f = FLAG_RE.exec(sentence)) !== null) {
76429
76543
  const flagThread = f[2];
76430
76544
  const raw3 = f[3].trim();
76545
+ if (/^vuln\b/i.test(raw3)) {
76546
+ const typed = /^vuln:\s*([\s\S]*)$/i.exec(raw3);
76547
+ const body2 = typed?.[1]?.trim() ?? "";
76548
+ const parts2 = body2.split(CAUSE_ARROW).map((s) => s.trim());
76549
+ const symptom = f[1] === "!" && parts2.length > 1 && !f[4] ? new RegExp(FLAG_RE.source, "i").exec(`[!${parts2[0]}]`) : null;
76550
+ const statement = f[1] === "?" ? body2 : symptom?.[3]?.trim() ?? parts2[0];
76551
+ if (statement.length < 8 || /^<[^>]*>$/.test(statement)) {
76552
+ dispositions.invalidVulnerability++;
76553
+ continue;
76554
+ }
76555
+ const path2 = (f[4] ?? symptom?.[4])?.trim();
76556
+ const line = f[5] ?? symptom?.[5];
76557
+ out2.push({
76558
+ kind: "vulnerability",
76559
+ statement,
76560
+ assessment: f[1] === "!" ? "asserted" : "suspected",
76561
+ ...f[1] === "!" && parts2.length > 1 ? { vulnerabilityCauses: parts2.slice(1).filter((s) => s.length >= CAUSE_TEXT_MIN) } : {},
76562
+ ...path2 ? { location: { path: path2, ...line ? { line: Number(line) } : {} } } : {},
76563
+ ...flagThread ? { threadId: flagThread } : {},
76564
+ sentence: sfield
76565
+ });
76566
+ continue;
76567
+ }
76431
76568
  if (FLAG_BODY_HANDLE_RE.test(raw3) && !f[4]) {
76432
76569
  dispositions.flagAsHandle++;
76433
76570
  out2.push({ kind: "prior", handle: raw3, sentence: sfield });
@@ -76798,7 +76935,7 @@ function harvestInlineTags(store, text, opts) {
76798
76935
  const parsed = parseInlineTagsWithDispositions(text);
76799
76936
  const tags = parsed.tags;
76800
76937
  plan.dispositions = parsed.dispositions;
76801
- const symptomSeqs = tags.filter((t) => (t.kind === "problem" || t.kind === "todo") && t.statement).map((t) => ({ seq: t.seq ?? -1, statement: t.statement, threadId: t.threadId })).sort((a, b) => a.seq - b.seq);
76938
+ const symptomSeqs = tags.filter((t) => (t.kind === "problem" || t.kind === "todo" || t.kind === "vulnerability") && t.statement).map((t) => ({ seq: t.seq ?? -1, statement: t.statement, threadId: t.threadId, kind: t.kind })).sort((a, b) => a.seq - b.seq);
76802
76939
  const bindSymptom = (seq, threadId) => {
76803
76940
  if (threadId) {
76804
76941
  const hit = symptomSeqs.find((s) => s.threadId === threadId);
@@ -76811,6 +76948,7 @@ function harvestInlineTags(store, text, opts) {
76811
76948
  if (seq == null) return { evidence: "inferred" };
76812
76949
  let best;
76813
76950
  for (const s of symptomSeqs) {
76951
+ if (s.kind === "vulnerability") continue;
76814
76952
  if (s.seq <= seq) best = s;
76815
76953
  else break;
76816
76954
  }
@@ -76825,10 +76963,13 @@ function harvestInlineTags(store, text, opts) {
76825
76963
  return id;
76826
76964
  };
76827
76965
  for (const tag of tags) {
76828
- if (tag.kind === "problem" || tag.kind === "todo") {
76966
+ if (tag.kind === "problem" || tag.kind === "todo" || tag.kind === "vulnerability") {
76829
76967
  plan.problems.push({
76830
76968
  statement: tag.statement,
76831
76969
  kind: tag.kind,
76970
+ ...tag.assessment ? { assessment: tag.assessment } : {},
76971
+ ...tag.vulnerabilityCauses ? { vulnerabilityCauses: tag.vulnerabilityCauses } : {},
76972
+ ...tag.kind === "vulnerability" && tag.seq !== void 0 ? { seq: tag.seq } : {},
76832
76973
  ...tag.location ? { location: tag.location } : {},
76833
76974
  ...tag.threadId ? { threadId: tag.threadId } : {}
76834
76975
  });
@@ -76851,7 +76992,7 @@ function harvestInlineTags(store, text, opts) {
76851
76992
  } else if (tag.threadId && !b.statement) {
76852
76993
  plan.fixes.push({ inScope: true, fixNote: tag.fixRationale, threadId: tag.threadId });
76853
76994
  } else if (b.statement && b.evidence === "witnessed") {
76854
- plan.fixes.push({ inScope: true, fixNote: tag.fixRationale, boundStatement: b.statement });
76995
+ plan.fixes.push({ inScope: true, fixNote: tag.fixRationale, boundStatement: b.statement, ...tag.threadId ? { threadId: tag.threadId } : {} });
76855
76996
  } else {
76856
76997
  plan.fixes.push({ inScope: true, fixNote: tag.fixRationale, ambiguous: true });
76857
76998
  }
@@ -77054,6 +77195,7 @@ function harvestInlineTags(store, text, opts) {
77054
77195
  }
77055
77196
  var CORROBORATABLE_LABELS = /* @__PURE__ */ new Set([
77056
77197
  "Problem",
77198
+ "Vulnerability",
77057
77199
  "Solution",
77058
77200
  "RootCause",
77059
77201
  "Pattern",
@@ -77746,6 +77888,15 @@ var PassWorker = class {
77746
77888
  nightly() {
77747
77889
  return this.call("nightly");
77748
77890
  }
77891
+ semanticMaintenance() {
77892
+ return this.call("semanticMaintenance");
77893
+ }
77894
+ liveness(invocations) {
77895
+ return this.call("liveness", [...invocations]);
77896
+ }
77897
+ retrievalInputs(repoLocator) {
77898
+ return this.call("retrievalInputs", repoLocator);
77899
+ }
77749
77900
  /** Reindex stale/drifted files on the worker; resolves to the count reindexed. */
77750
77901
  reconcile() {
77751
77902
  return this.call("reconcile");
@@ -78873,6 +79024,13 @@ function scanManifests(root) {
78873
79024
  function readManifestDependencies(root) {
78874
79025
  return scanManifests(root).deps;
78875
79026
  }
79027
+ function readRetrievalInputs(root, repoLocator) {
79028
+ return {
79029
+ direct: readDirectDependencies(root),
79030
+ manifestDeps: readManifestDependencies(root),
79031
+ registry: loadRegistryPolicy(root, process.env, repoLocator)
79032
+ };
79033
+ }
78876
79034
 
78877
79035
  // src/git-sensor.ts
78878
79036
  import { existsSync as existsSync23, readFileSync as readFileSync23, watch as fsWatch } from "node:fs";
@@ -80832,9 +80990,9 @@ function partitionReplayable(queue, channel, isShipped) {
80832
80990
  function retireWitnesses(queue, channel, settledKeys) {
80833
80991
  return queue.filter((w) => !(w.channel === channel && settledKeys.has(w.witnessKey)));
80834
80992
  }
80835
- var WIRE_SHIPPED_LABELS = /* @__PURE__ */ new Set(["Problem", "Solution", "RootCause"]);
80993
+ var WIRE_SHIPPED_LABELS = /* @__PURE__ */ new Set(["Problem", "Vulnerability", "Solution", "RootCause"]);
80836
80994
  var MOTIF_ID_PREFIX = "motif_community_";
80837
- var CANONICAL_ID_RE = /^(?:pat|sol|drc|dcause|dfix|dprob|prob|claim|err)_[0-9a-f]{6,}$/i;
80995
+ var CANONICAL_ID_RE = /^(?:pat|sol|drc|dcause|dfix|dprob|dvuln|prob|claim|err)_[0-9a-f]{6,}$/i;
80838
80996
  function classifyWitnessTarget(nodeId, getNode) {
80839
80997
  if (nodeId.startsWith(MOTIF_ID_PREFIX)) {
80840
80998
  const exemplar = nodeId.slice(MOTIF_ID_PREFIX.length);
@@ -81241,11 +81399,13 @@ function saveAlarmState(configDir, state) {
81241
81399
  }
81242
81400
  function createLivenessWatch(deps) {
81243
81401
  let lastRunAt = 0;
81402
+ let running = false;
81244
81403
  const lastAlarmedAt = loadAlarmState(deps.configDir);
81245
81404
  return {
81246
- maybeRun(nowMs = Date.now()) {
81247
- if (nowMs - lastRunAt < LIVENESS_WATCH_INTERVAL_MS) return null;
81405
+ async maybeRun(nowMs = Date.now()) {
81406
+ if (running || nowMs - lastRunAt < LIVENESS_WATCH_INTERVAL_MS) return null;
81248
81407
  lastRunAt = nowMs;
81408
+ running = true;
81249
81409
  try {
81250
81410
  const started2 = Date.now();
81251
81411
  const invocations = /* @__PURE__ */ new Map();
@@ -81259,7 +81419,7 @@ function createLivenessWatch(deps) {
81259
81419
  lastTotals: s.lastTotals
81260
81420
  });
81261
81421
  }
81262
- const rows = evaluateMechanisms(deps.store, invocations);
81422
+ const rows = deps.evaluate ? await deps.evaluate(invocations) : evaluateMechanisms(deps.store, invocations);
81263
81423
  const workspaceActive = invocations.has("capture") || invocations.has("render");
81264
81424
  const bad = workspaceActive ? rows.filter((r) => hasStarvedMechanism([r])) : [];
81265
81425
  const watch3 = watchCensus();
@@ -81296,13 +81456,42 @@ function createLivenessWatch(deps) {
81296
81456
  }
81297
81457
  }
81298
81458
  return rows;
81299
- } catch {
81459
+ } catch (err2) {
81460
+ console.warn("[errata] liveness watch failed:", err2 instanceof Error ? err2.message : String(err2));
81300
81461
  return null;
81462
+ } finally {
81463
+ running = false;
81301
81464
  }
81302
81465
  }
81303
81466
  };
81304
81467
  }
81305
81468
 
81469
+ // src/workspace-maintenance.ts
81470
+ init_src8();
81471
+ init_src5();
81472
+ function runWorkspaceMaintenance(store, workspaceId2) {
81473
+ const seqBefore = store.currentIngestSeq();
81474
+ const report = runSemanticMaintenance(store);
81475
+ let hintUpgrade = { upgraded: 0, standing: 0, unresolvable: 0, blocked: 0 };
81476
+ try {
81477
+ hintUpgrade = upgradeAnchorHints(store, workspaceId2, Date.now());
81478
+ } catch {
81479
+ }
81480
+ let calibration = null;
81481
+ const calibrationStarted = Date.now();
81482
+ try {
81483
+ calibration = wmCalibrationCells(store);
81484
+ } catch {
81485
+ }
81486
+ return {
81487
+ report,
81488
+ hintUpgrade,
81489
+ seqAdvanced: store.currentIngestSeq() - seqBefore,
81490
+ calibration,
81491
+ calibrationMs: Date.now() - calibrationStarted
81492
+ };
81493
+ }
81494
+
81306
81495
  // src/watch-breaker.ts
81307
81496
  function numEnv3(key, fallback) {
81308
81497
  const n = Number(process.env[key]);
@@ -81404,7 +81593,7 @@ function createWatchBreaker(deps) {
81404
81593
  }
81405
81594
 
81406
81595
  // src/engine.ts
81407
- var DAEMON_VERSION = true ? "2.0.16" : "2.0.0-alpha.0";
81596
+ var DAEMON_VERSION = true ? "2.0.17-dev.3407" : "2.0.0-alpha.0";
81408
81597
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
81409
81598
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
81410
81599
  var GIT_OP_MUTE_MS = 4e3;
@@ -81791,6 +81980,7 @@ function createWorkspaceEngine(opts) {
81791
81980
  const livenessWatch = createLivenessWatch({
81792
81981
  configDir: paths.configDir,
81793
81982
  store,
81983
+ ...passWorker ? { evaluate: (invocations) => passWorker.liveness(invocations) } : {},
81794
81984
  onAlarm: (id, line) => {
81795
81985
  console.warn(`[errata] mechanism not working: ${line}`);
81796
81986
  notifyEvent("mechanism-stalled", line, { key: id });
@@ -81956,21 +82146,22 @@ function createWorkspaceEngine(opts) {
81956
82146
  contextDirty = false;
81957
82147
  notifyDigest("recall");
81958
82148
  };
81959
- const outboundStack = () => retrievalStackTokens({
81960
- direct: readDirectDependencies(opts.workspaceRoot),
81961
- manifestDeps: readManifestDependencies(opts.workspaceRoot),
81962
- profileStack: profile?.stack ?? [],
81963
- // Languages ride the stack list: the cloud's context-affinity matcher
81964
- // resolves each token against both the Package and Language namespaces,
81965
- // and the profile keeps them in separate fields.
81966
- languages: profile?.languages ?? [],
81967
- contributePackages: consentNow().contributePackages,
81968
- // `repoLocator` feeds the Maven self-disclosure rule ONLY: a groupId
81969
- // carrying this project's own owner/repo token is the project naming
81970
- // itself, not a library it depends on.
81971
- registry: loadRegistryPolicy(opts.workspaceRoot, process.env, profile?.repoLocator),
81972
- ignorePatterns: loadClaimIgnorePatterns(opts.workspaceRoot)
81973
- }).tokens;
82149
+ const outboundStack = async () => {
82150
+ const inputs = passWorker ? await passWorker.retrievalInputs(profile?.repoLocator) : readRetrievalInputs(opts.workspaceRoot, profile?.repoLocator);
82151
+ return retrievalStackTokens({
82152
+ ...inputs,
82153
+ profileStack: profile?.stack ?? [],
82154
+ // Languages ride the stack list: the cloud's context-affinity matcher
82155
+ // resolves each token against both the Package and Language namespaces,
82156
+ // and the profile keeps them in separate fields.
82157
+ languages: profile?.languages ?? [],
82158
+ contributePackages: consentNow().contributePackages,
82159
+ // `repoLocator` feeds the Maven self-disclosure rule ONLY: a groupId
82160
+ // carrying this project's own owner/repo token is the project naming
82161
+ // itself, not a library it depends on.
82162
+ ignorePatterns: loadClaimIgnorePatterns(opts.workspaceRoot)
82163
+ }).tokens;
82164
+ };
81974
82165
  const maybeRefreshRemotePriors = () => {
81975
82166
  if (opts.skipContextWriter || remoteInFlight) return;
81976
82167
  const now = Date.now();
@@ -81980,11 +82171,12 @@ function createWorkspaceEngine(opts) {
81980
82171
  if (typeof cloud.search !== "function") return;
81981
82172
  remoteInFlight = true;
81982
82173
  lastRemoteAt = now;
81983
- try {
82174
+ void outboundStack().then((stack) => {
82175
+ if (!(opts.syncConsent ?? consentNow().sync)) return;
81984
82176
  const norm = normStackTokens;
81985
82177
  const seed = store.recentNodeIds("Problem", 6);
81986
82178
  const q = {
81987
- stack: outboundStack(),
82179
+ stack,
81988
82180
  domains: norm(profile.domains),
81989
82181
  kinds: ["Pattern", "Technique", "AntiPattern"],
81990
82182
  ...seed.length ? { seed } : {},
@@ -81994,7 +82186,7 @@ function createWorkspaceEngine(opts) {
81994
82186
  channel: "priming",
81995
82187
  limit: 8
81996
82188
  };
81997
- cloud.search(q).then((res) => {
82189
+ return cloud.search(q).then((res) => {
81998
82190
  remotePriors = res.nodes ?? [];
81999
82191
  const contributorLabels = [];
82000
82192
  for (const n of remotePriors) {
@@ -82004,13 +82196,11 @@ function createWorkspaceEngine(opts) {
82004
82196
  }
82005
82197
  if (contributorLabels.length > 0) registerRenderedContributorLabels(contributorLabels);
82006
82198
  contextDirty = true;
82007
- }).catch(() => {
82008
- }).finally(() => {
82009
- remoteInFlight = false;
82010
82199
  });
82011
- } catch {
82200
+ }).catch(() => {
82201
+ }).finally(() => {
82012
82202
  remoteInFlight = false;
82013
- }
82203
+ });
82014
82204
  };
82015
82205
  const maybeRefreshIntentPriors = (liveIntent) => {
82016
82206
  if (!liveIntent || opts.skipContextWriter || intentRemoteInFlight) return;
@@ -82019,22 +82209,26 @@ function createWorkspaceEngine(opts) {
82019
82209
  if (!syncOk) return;
82020
82210
  if (typeof cloud.search !== "function") return;
82021
82211
  intentRemoteInFlight = true;
82022
- cloud.search({
82023
- // VS-context-affinity: the intent lane used to send an empty context
82024
- // while the priming lane sent the profile — opposite postures on the
82025
- // same method. Under "union derived + stated, canonicalize once"
82026
- // (VS-intent-fence) the stated intent IS the query and the derived
82027
- // stack rides along as structured relevanceContext: the cloud no
82028
- // longer concatenates these tokens into the embedding string, it
82029
- // re-weights anchored hits with them (stated beats derived — the
82030
- // intent text drives entry search, the stack only re-ranks).
82031
- stack: outboundStack(),
82032
- domains: normStackTokens(profile?.domains ?? []),
82033
- kinds: ["Pattern", "Technique", "AntiPattern", "Problem", "Solution"],
82034
- q: liveIntent,
82035
- channel: "intent",
82036
- limit: 6
82212
+ void outboundStack().then((stack) => {
82213
+ if (!(opts.syncConsent ?? consentNow().sync)) return;
82214
+ return cloud.search({
82215
+ // VS-context-affinity: the intent lane used to send an empty context
82216
+ // while the priming lane sent the profile — opposite postures on the
82217
+ // same method. Under "union derived + stated, canonicalize once"
82218
+ // (VS-intent-fence) the stated intent IS the query and the derived
82219
+ // stack rides along as structured relevanceContext: the cloud no
82220
+ // longer concatenates these tokens into the embedding string, it
82221
+ // re-weights anchored hits with them (stated beats derived — the
82222
+ // intent text drives entry search, the stack only re-ranks).
82223
+ stack,
82224
+ domains: normStackTokens(profile?.domains ?? []),
82225
+ kinds: ["Pattern", "Technique", "AntiPattern", "Problem", "Solution"],
82226
+ q: liveIntent,
82227
+ channel: "intent",
82228
+ limit: 6
82229
+ });
82037
82230
  }).then((res) => {
82231
+ if (!res) return;
82038
82232
  intentRemote = {
82039
82233
  forIntent: liveIntent,
82040
82234
  hits: res.nodes ?? [],
@@ -82094,7 +82288,7 @@ function createWorkspaceEngine(opts) {
82094
82288
  if (isWorkerOom(err2)) {
82095
82289
  console.warn("[errata] nightly worker hit its heap ceiling \u2014 heavy pass skipped this fire");
82096
82290
  appendPassLedger(paths.configDir, "graph-rescore", 0, { oomSkipped: 1 });
82097
- const sem = runSemanticMaintenance(store);
82291
+ const { report: sem } = await passWorker.semanticMaintenance();
82098
82292
  return {
82099
82293
  report: {
82100
82294
  scoredNodes: 0,
@@ -82296,6 +82490,9 @@ function createWorkspaceEngine(opts) {
82296
82490
  const harvestTextsInner = async (sessionId, items) => {
82297
82491
  let minted = 0;
82298
82492
  let tagsSeen = 0;
82493
+ let vulnerabilitiesSeen = 0;
82494
+ let vulnerabilitiesMinted = 0;
82495
+ let vulnerabilitiesSuspected = 0;
82299
82496
  let tagsRejected = 0;
82300
82497
  let tagsRestated = 0;
82301
82498
  let resolved = 0;
@@ -82526,10 +82723,73 @@ function createWorkspaceEngine(opts) {
82526
82723
  if ((toolRuns.get(sessionId)?.size ?? 0) > 0) touchedToolTurns++;
82527
82724
  const wf = workingFiles.get(sessionId);
82528
82725
  let inScopeProblemId;
82726
+ const findingIds = /* @__PURE__ */ new Map();
82727
+ const conflictingThreads = /* @__PURE__ */ new Set();
82529
82728
  const threads = sessionThreads.get(sessionId) ?? /* @__PURE__ */ new Map();
82530
82729
  sessionThreads.set(sessionId, threads);
82531
82730
  for (const p of plan.problems) {
82532
82731
  tagsSeen++;
82732
+ if (p.kind === "vulnerability") {
82733
+ vulnerabilitiesSeen++;
82734
+ if (p.assessment === "suspected") vulnerabilitiesSuspected++;
82735
+ let fileText;
82736
+ let anchorReadFailed = false;
82737
+ if (p.location) {
82738
+ try {
82739
+ fileText = readFileSync31(join35(opts.workspaceRoot, p.location.path), "utf8");
82740
+ } catch {
82741
+ anchorReadFailed = true;
82742
+ }
82743
+ }
82744
+ const result = ingestWitnessedVulnerability(store, {
82745
+ statement: p.statement,
82746
+ assessment: p.assessment,
82747
+ target: p.location?.path,
82748
+ line: p.location?.line,
82749
+ fileText,
82750
+ existingId: p.threadId ? threads.get(p.threadId) ?? handleMap[p.threadId]?.id ?? store.getNode(p.threadId)?.id : void 0
82751
+ }, { workspaceId: profile.id, source: sessionId, ts: t, hostHarness: turn.hostHarness });
82752
+ let anchorDisposition = p.location ? "unresolved" : "absent";
82753
+ if (result.rejected) {
82754
+ tagsRejected++;
82755
+ if (p.threadId) conflictingThreads.add(p.threadId);
82756
+ } else {
82757
+ findingIds.set(p.statement, result.id);
82758
+ if (p.threadId) threads.set(p.threadId, result.id);
82759
+ if (result.created || result.corroborated || result.upgraded) minted++;
82760
+ else tagsRestated++;
82761
+ if (result.created) vulnerabilitiesMinted++;
82762
+ if (p.location) {
82763
+ try {
82764
+ const anchored = anchorProblemToWorkingFile(store, result.id, p.location.path, profile.id, t, "witnessed", { line: p.location.line, fileText });
82765
+ anchorDisposition = anchored ? "resolved" : anchorReadFailed ? "error" : "unresolved";
82766
+ if (anchored) deriveContextFromAnchors(store, result.id, t);
82767
+ } catch {
82768
+ anchorDisposition = "error";
82769
+ }
82770
+ }
82771
+ const node2 = store.getNode(result.id);
82772
+ store.updateNode(result.id, { attrs: { ...node2.attrs, anchorDisposition } });
82773
+ if (p.vulnerabilityCauses) triaged += recordVulnerabilityCause(store, result.id, p.vulnerabilityCauses, t);
82774
+ }
82775
+ log.append({
82776
+ kind: "vulnerability.witness",
82777
+ workspaceId: profile.id,
82778
+ ts: t,
82779
+ digest: digest({ kind: "vulnerability.witness", workspaceId: profile.id, sessionId, turnId: turn.uuid, statement: p.statement, assessment: p.assessment, location: p.location }),
82780
+ sessionId,
82781
+ turnId: turn.uuid,
82782
+ harness: turn.hostHarness,
82783
+ findingId: result.id,
82784
+ statement: p.statement,
82785
+ assessment: p.assessment,
82786
+ location: p.location,
82787
+ seq: p.seq,
82788
+ anchorDisposition,
82789
+ disposition: result.rejected ?? (result.created ? "created" : result.upgraded ? "upgraded" : result.corroborated ? "corroborated" : "restated")
82790
+ });
82791
+ continue;
82792
+ }
82533
82793
  const anchorPath = p.location?.path ?? editedTurnFile;
82534
82794
  const anchorProvenance = p.location?.path ? "witnessed" : "edited";
82535
82795
  const hintPath = anchorPath ? void 0 : turnFile ?? wf;
@@ -82624,10 +82884,10 @@ function createWorkspaceEngine(opts) {
82624
82884
  if (r.created || r.corroborated) {
82625
82885
  minted++;
82626
82886
  if (p.kind !== "constraint") {
82627
- sessionLastProblem.set(sessionId, designProblemId(p.statement));
82628
- inScopeProblemId = designProblemId(p.statement);
82887
+ sessionLastProblem.set(sessionId, r.problemId);
82888
+ inScopeProblemId = r.problemId;
82629
82889
  }
82630
- if (p.threadId) threads.set(p.threadId, designProblemId(p.statement));
82890
+ if (p.threadId) threads.set(p.threadId, r.problemId);
82631
82891
  try {
82632
82892
  if (anchorPath) {
82633
82893
  let enclosing = {};
@@ -82664,7 +82924,8 @@ function createWorkspaceEngine(opts) {
82664
82924
  );
82665
82925
  }
82666
82926
  for (const fx of plan.fixes) {
82667
- const pid = fx.problemId ?? (fx.threadId ? threads.get(fx.threadId) : void 0) ?? (fx.boundStatement ? designProblemId(fx.boundStatement) : void 0);
82927
+ if (fx.threadId && conflictingThreads.has(fx.threadId)) continue;
82928
+ const pid = fx.problemId ?? (fx.threadId ? threads.get(fx.threadId) : void 0) ?? (fx.boundStatement ? findingIds.get(fx.boundStatement) ?? designProblemId(fx.boundStatement) : void 0);
82668
82929
  if (!pid) {
82669
82930
  if (fx.inScope) {
82670
82931
  console.warn(
@@ -82676,7 +82937,7 @@ function createWorkspaceEngine(opts) {
82676
82937
  const closed = resolveDesignProblemById(store, pid, fx.fixNote, t);
82677
82938
  if (!closed) {
82678
82939
  const n = store.getNode(pid);
82679
- if (!n || n.label !== "Problem" || n.attrs["resolvedAs"] != null) {
82940
+ if (!n || n.label !== "Problem" && n.label !== "Vulnerability" || n.attrs["resolvedAs"] != null) {
82680
82941
  console.warn(
82681
82942
  `[errata] fix tag went nowhere: ${!n ? `id ${pid.slice(0, 32)} unknown` : n.label !== "Problem" ? `${pid.slice(0, 32)} is a ${n.label}, not a Problem` : "the problem was retracted \u2014 a retraction is not fixable"} \u2014 the fix was NOT recorded`
82682
82943
  );
@@ -82695,10 +82956,23 @@ function createWorkspaceEngine(opts) {
82695
82956
  }
82696
82957
  }
82697
82958
  }
82959
+ const vulnerabilityTriages = /* @__PURE__ */ new Set();
82960
+ for (const tr of plan.triages) {
82961
+ if (tr.threadId && conflictingThreads.has(tr.threadId)) {
82962
+ vulnerabilityTriages.add(tr);
82963
+ continue;
82964
+ }
82965
+ const id = tr.symptomProblemId ?? (tr.threadId ? threads.get(tr.threadId) : void 0) ?? (tr.symptomStatement ? findingIds.get(tr.symptomStatement) : void 0);
82966
+ if (id && store.getNode(id)?.label === "Vulnerability") {
82967
+ vulnerabilityTriages.add(tr);
82968
+ if (tr.evidence === "witnessed" && tr.causeDescription) triaged += recordVulnerabilityCause(store, id, [tr.causeDescription], t);
82969
+ }
82970
+ }
82698
82971
  if (opts.sharedStore && plan.triages.length > 0) {
82699
82972
  const fallbackId = sessionLastProblem.get(sessionId);
82700
82973
  const fallbackStatement = fallbackId ? store.getNode(fallbackId)?.description : void 0;
82701
82974
  for (const tr of plan.triages) {
82975
+ if (vulnerabilityTriages.has(tr)) continue;
82702
82976
  let presentingStatement = tr.symptomStatement;
82703
82977
  let evidence = tr.evidence;
82704
82978
  if (!presentingStatement && tr.threadId) {
@@ -82716,7 +82990,7 @@ function createWorkspaceEngine(opts) {
82716
82990
  evidence = "inferred";
82717
82991
  }
82718
82992
  if (!presentingStatement) continue;
82719
- const presentingId = tr.symptomProblemId ?? designProblemId(presentingStatement);
82993
+ const presentingId = tr.symptomProblemId ?? (tr.threadId ? threads.get(tr.threadId) : void 0) ?? designProblemId(presentingStatement);
82720
82994
  if (tr.causeId === presentingId) continue;
82721
82995
  try {
82722
82996
  recordTriageObservation(
@@ -82774,7 +83048,7 @@ function createWorkspaceEngine(opts) {
82774
83048
  }
82775
83049
  }
82776
83050
  for (const at of plan.attempts) {
82777
- const pid = at.problemId ?? (at.threadId ? threads.get(at.threadId) : void 0) ?? (at.boundStatement ? designProblemId(at.boundStatement) : void 0);
83051
+ const pid = at.problemId ?? (at.threadId ? threads.get(at.threadId) : void 0) ?? (at.boundStatement ? findingIds.get(at.boundStatement) ?? designProblemId(at.boundStatement) : void 0);
82778
83052
  const node2 = pid ? store.getNode(pid) : void 0;
82779
83053
  if (!pid || !node2) continue;
82780
83054
  try {
@@ -82790,7 +83064,7 @@ function createWorkspaceEngine(opts) {
82790
83064
  }
82791
83065
  }
82792
83066
  const bindPid = (tag) => {
82793
- const pid = tag.problemId ?? (tag.threadId ? threads.get(tag.threadId) : void 0) ?? (tag.boundStatement ? designProblemId(tag.boundStatement) : void 0) ?? inScopeProblemId ?? sessionLastProblem.get(sessionId);
83067
+ const pid = tag.problemId ?? (tag.threadId ? threads.get(tag.threadId) : void 0) ?? (tag.boundStatement ? findingIds.get(tag.boundStatement) ?? designProblemId(tag.boundStatement) : void 0) ?? inScopeProblemId ?? sessionLastProblem.get(sessionId);
82794
83068
  return pid && store.getNode(pid) ? pid : void 0;
82795
83069
  };
82796
83070
  const mintCiteEdge = (from, to, type, confidence, extraAttrs) => {
@@ -82944,7 +83218,12 @@ function createWorkspaceEngine(opts) {
82944
83218
  for (const [session, items2] of groups) {
82945
83219
  if (items2.length === 0) continue;
82946
83220
  try {
82947
- const raw2 = await send(items2, session);
83221
+ const wireItems = items2.map(({ nodeId, witnessKey }) => {
83222
+ const node2 = store.getNode(nodeId);
83223
+ const cloudId = node2?.attrs["privateFinding"] ? node2.attrs["cloudNodeId"] : void 0;
83224
+ return { nodeId: typeof cloudId === "string" ? cloudId : nodeId, witnessKey };
83225
+ });
83226
+ const raw2 = await send(wireItems, session);
82948
83227
  const d = {
82949
83228
  recorded: raw2?.recorded ?? 0,
82950
83229
  unmatched: raw2?.unmatched ?? 0,
@@ -82957,8 +83236,8 @@ function createWorkspaceEngine(opts) {
82957
83236
  duplicate += d.duplicate;
82958
83237
  selfGated += d.selfGated;
82959
83238
  const missing = new Set(d.unmatchedIds);
82960
- for (const it of items2) {
82961
- if (missing.has(it.nodeId)) stillUnmatched.push({ channel, ...it, sessionKey: session, counted: isShipped(it.nodeId) });
83239
+ for (const [index, it] of items2.entries()) {
83240
+ if (missing.has(wireItems[index].nodeId)) stillUnmatched.push({ channel, ...it, sessionKey: session, counted: isShipped(it.nodeId) });
82962
83241
  else settled.add(it.witnessKey);
82963
83242
  }
82964
83243
  } catch (err2) {
@@ -83128,6 +83407,10 @@ function createWorkspaceEngine(opts) {
83128
83407
  // but nothing judgeable was cited.
83129
83408
  tagsVerdictNoHandle: dispositions.verdictNoHandle,
83130
83409
  tagsFlagAsHandle: dispositions.flagAsHandle,
83410
+ tagsInvalidVulnerability: dispositions.invalidVulnerability,
83411
+ vulnerabilitiesSeen,
83412
+ vulnerabilitiesMinted,
83413
+ vulnerabilitiesSuspected,
83131
83414
  unresolvedHandles,
83132
83415
  priorEdgesFlagOff,
83133
83416
  priorEdgesNoSource,
@@ -83525,15 +83808,7 @@ B: ${b}`,
83525
83808
  return report;
83526
83809
  },
83527
83810
  async semanticMaintenance() {
83528
- const yieldLoop = () => new Promise((r) => setImmediate(r));
83529
- const seqBefore = store.currentIngestSeq();
83530
- const report = runSemanticMaintenance(store);
83531
- await yieldLoop();
83532
- let hintUpgrade = { upgraded: 0, standing: 0, unresolvable: 0, blocked: 0 };
83533
- try {
83534
- hintUpgrade = upgradeAnchorHints(store, profile.id, Date.now());
83535
- } catch {
83536
- }
83811
+ const { report, hintUpgrade, seqAdvanced, calibration, calibrationMs } = passWorker ? await passWorker.semanticMaintenance() : runWorkspaceMaintenance(store, profile.id);
83537
83812
  appendPassLedger(paths.configDir, "semantic-maintenance", report.durationMs, {
83538
83813
  revisitsCleared: report.revisitsCleared,
83539
83814
  revisitsStanding: report.revisitsStanding,
@@ -83544,7 +83819,7 @@ B: ${b}`,
83544
83819
  fixCandidatesStanding: report.fixCandidates.standing,
83545
83820
  designResolved: report.designResolved,
83546
83821
  claimsEvaluated: report.claimsEvaluated,
83547
- seqAdvanced: store.currentIngestSeq() - seqBefore,
83822
+ seqAdvanced,
83548
83823
  anchorHintsUpgraded: hintUpgrade.upgraded,
83549
83824
  anchorHintsStanding: hintUpgrade.standing,
83550
83825
  anchorHintsUnresolvable: hintUpgrade.unresolvable,
@@ -83553,19 +83828,15 @@ B: ${b}`,
83553
83828
  if (report.revisitsCleared > 0 || report.fixCandidatesSettled > 0 || report.designResolved > 0) {
83554
83829
  refreshContextNow();
83555
83830
  }
83556
- await yieldLoop();
83557
- try {
83558
- const t0 = Date.now();
83831
+ if (calibration) {
83559
83832
  appendPassLedger(
83560
83833
  paths.configDir,
83561
83834
  "wm-calibration",
83562
- Date.now() - t0,
83563
- wmCalibrationCells(store)
83835
+ calibrationMs,
83836
+ calibration
83564
83837
  );
83565
- } catch {
83566
83838
  }
83567
- await yieldLoop();
83568
- livenessWatch.maybeRun();
83839
+ await livenessWatch.maybeRun();
83569
83840
  return report;
83570
83841
  },
83571
83842
  async nightly() {
@@ -84465,7 +84736,7 @@ function internalSymbolsFor(description, lexiconHas, publicIndex) {
84465
84736
 
84466
84737
  // src/instance-ingest.ts
84467
84738
  init_generalize_graph();
84468
- var INSTANCE_LABELS = ["Problem", "Solution", "RootCause"];
84739
+ var INSTANCE_LABELS = ["Problem", "Vulnerability", "Solution", "RootCause"];
84469
84740
  var INSTANCE_EDGES = ["CAUSED_BY", "SOLVED_BY"];
84470
84741
  var ANCHOR_EDGES = ["OCCURS_IN", "DEPENDS_ON", "PERTAIN_TO", "CONCERNS"];
84471
84742
  var ANCHOR_TARGET_LABELS = ["Language", "Package", "Domain", "Component"];
@@ -84475,7 +84746,7 @@ function stripCodebaseScope(scope) {
84475
84746
  return s;
84476
84747
  }
84477
84748
  function compositionCounters(c) {
84478
- const { sidecarDropReasons, ...scalars } = c;
84749
+ const { sidecarDropReasons, ...scalars2 } = c;
84479
84750
  const buckets = [
84480
84751
  "empty",
84481
84752
  "too_long",
@@ -84485,7 +84756,7 @@ function compositionCounters(c) {
84485
84756
  "other"
84486
84757
  ];
84487
84758
  return {
84488
- ...scalars,
84759
+ ...scalars2,
84489
84760
  ...Object.fromEntries(buckets.map((b) => [`sidecarDrop_${b}`, sidecarDropReasons[b] ?? 0]))
84490
84761
  };
84491
84762
  }
@@ -84600,6 +84871,10 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
84600
84871
  attrs: {
84601
84872
  scope: stripCodebaseScope(n.attrs["scope"]),
84602
84873
  ...n.attrs["kind"] ? { kind: n.attrs["kind"] } : {},
84874
+ ...n.attrs["privateFinding"] ? { privateFinding: true } : {},
84875
+ ...n.attrs["assessment"] ? { assessment: n.attrs["assessment"] } : {},
84876
+ ...n.attrs["sourceClass"] ? { sourceClass: n.attrs["sourceClass"] } : {},
84877
+ ...n.attrs["resolutionWitnessed"] ? { resolutionWitnessed: true, resolvedAt: n.attrs["resolvedAt"] } : {},
84603
84878
  ...n.attrs["resolvedAs"] ? { resolvedAs: n.attrs["resolvedAs"] } : {},
84604
84879
  ...preserved.length > 0 ? { preservedSymbols: preserved } : {},
84605
84880
  ...internal.length > 0 ? { internalSymbols: internal } : {}
@@ -84614,6 +84889,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
84614
84889
  const semanticEndpoints = /* @__PURE__ */ new Map();
84615
84890
  const includedByLabel = /* @__PURE__ */ new Map();
84616
84891
  const composition = {
84892
+ privateTargetMissing: 0,
84617
84893
  candidates: 0,
84618
84894
  shipped: 0,
84619
84895
  cloudSourced: 0,
@@ -84659,11 +84935,15 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
84659
84935
  if (walked.has(n.id)) continue;
84660
84936
  walked.add(n.id);
84661
84937
  composition.candidates++;
84938
+ if ((n.attrs["privateFinding"] || n.label === "Vulnerability") && !opts.project) {
84939
+ composition.privateTargetMissing++;
84940
+ continue;
84941
+ }
84662
84942
  if (n.attrs["source"] === "cloud") {
84663
84943
  composition.cloudSourced++;
84664
84944
  continue;
84665
84945
  }
84666
- if (label === "Problem" && n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) {
84946
+ if (n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) {
84667
84947
  composition.falsePositive++;
84668
84948
  continue;
84669
84949
  }
@@ -84686,7 +84966,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
84686
84966
  composition.terminalRefused++;
84687
84967
  continue;
84688
84968
  }
84689
- if (!noveltyAgainst(n, ref, noveltyOpts).ready) {
84969
+ if (!n.attrs["privateFinding"] && !noveltyAgainst(n, ref, noveltyOpts).ready) {
84690
84970
  composition.notNovel++;
84691
84971
  continue;
84692
84972
  }
@@ -84729,7 +85009,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
84729
85009
  }
84730
85010
  if (targets.length === 0) continue;
84731
85011
  const shipped = shippedById.get(sourceId);
84732
- if (shipped) {
85012
+ if (shipped && !shipped.attrs["privateFinding"]) {
84733
85013
  const anchors = (label) => targets.filter((x) => x.t.label === label).map(
84734
85014
  (x) => label === "Language" ? langAnchor(x.t) : label === "Component" ? `component:${x.wireId}` : x.wireId
84735
85015
  ).filter((a) => a != null).sort();
@@ -85511,38 +85791,41 @@ function classifyAnchorVersion(cloudAttrs, localSymbol) {
85511
85791
  if (typeof seen !== "string" || typeof have !== "string") return "unverified";
85512
85792
  return seen === have ? "current" : "pre-change";
85513
85793
  }
85514
- function buildReverseSymbolMap(opts) {
85515
- return collectFirstWins(reverseSymbolEntries(opts));
85794
+ async function buildReverseSymbolMap(opts) {
85795
+ return collectFirstWinsYielding(reverseSymbolEntries(opts));
85516
85796
  }
85517
85797
  function* reverseSymbolEntries(opts) {
85518
85798
  const { store, salt, projectId } = opts;
85519
85799
  for (const kind of ANCHOR_SYMBOL_KINDS2) {
85520
85800
  for (const n of store.iterateNodeIdentitiesByLabel(kind)) {
85521
85801
  const { relPath, qname } = n;
85522
- if (relPath === null || qname === null) continue;
85802
+ if (relPath === null || qname === null) {
85803
+ yield null;
85804
+ continue;
85805
+ }
85523
85806
  const sym = projectSymbolId(salt, projectId, relPath, qname, kind);
85524
85807
  yield [sym, n.id];
85525
85808
  yield [projectSaltedNodeId(sym, salt), n.id];
85526
85809
  }
85527
85810
  }
85528
85811
  for (const n of store.iterateNodeIdentitiesByLabel("File")) {
85529
- if (n.relPath === null) continue;
85812
+ if (n.relPath === null) {
85813
+ yield null;
85814
+ continue;
85815
+ }
85530
85816
  const sym = projectSymbolId(salt, projectId, n.relPath, FILE_ANCHOR_QNAME, "File");
85531
85817
  yield [sym, n.id];
85532
85818
  yield [projectSaltedNodeId(sym, salt), n.id];
85533
85819
  }
85534
85820
  }
85535
- function collectFirstWins(entries) {
85536
- const m = /* @__PURE__ */ new Map();
85537
- for (const [k, v] of entries) if (!m.has(k)) m.set(k, v);
85538
- return m;
85539
- }
85540
85821
  var MAP_BUILD_BATCH = 2e3;
85541
85822
  async function collectFirstWinsYielding(entries) {
85542
85823
  const m = /* @__PURE__ */ new Map();
85543
85824
  let i2 = 0;
85544
- for (const [k, v] of entries) {
85825
+ for (const entry of entries) {
85545
85826
  if (++i2 % MAP_BUILD_BATCH === 0) await new Promise((r) => setImmediate(r));
85827
+ if (entry === null) continue;
85828
+ const [k, v] = entry;
85546
85829
  if (!m.has(k)) m.set(k, v);
85547
85830
  }
85548
85831
  return m;
@@ -85634,7 +85917,7 @@ async function reanchorProject(opts) {
85634
85917
  if (!profile.projectId) return { reanchored: false, reason: "no-project" };
85635
85918
  if (profile.projectReanchoredAt) return { reanchored: false, reason: "already-reanchored" };
85636
85919
  if (!profile.projectHydratedAt) return { reanchored: false, reason: "not-hydrated" };
85637
- const reverse = buildReverseSymbolMap({ store, salt, projectId: profile.projectId });
85920
+ const reverse = await buildReverseSymbolMap({ store, salt, projectId: profile.projectId });
85638
85921
  if (reverse.size === 0) return { reanchored: false, reason: "no-local-symbols" };
85639
85922
  try {
85640
85923
  const res = await cloud.getProjectPriors(profile.projectId, opts.limit ?? DEFAULT_LIMIT);
@@ -85696,7 +85979,7 @@ async function syncProjectAnchors(opts) {
85696
85979
  reverseCache,
85697
85980
  store,
85698
85981
  key,
85699
- () => collectFirstWinsYielding(reverseSymbolEntries({ store, salt, projectId }))
85982
+ () => buildReverseSymbolMap({ store, salt, projectId })
85700
85983
  );
85701
85984
  const own = cachedMap(ownCache, store, salt, () => collectLastWinsYielding(ownKnowledgeEntries({ store, salt })));
85702
85985
  const limit = opts.limit ?? SYNC_PAGE;
@@ -85869,11 +86152,11 @@ async function syncHistoryRevisionPage(opts) {
85869
86152
  const sweepCommit = after ? oldCursor.atCommit : atCommit;
85870
86153
  const page = await cloud.getHistoryOccurrences(projectId, atCommit, after);
85871
86154
  if (!Array.isArray(page.occurrences) || page.occurrences.length > 50 || page.occurrences.some((o, i2) => o.occurrenceId <= (page.occurrences[i2 - 1]?.occurrenceId ?? after ?? "")) || page.cursor !== null && page.cursor !== page.occurrences.at(-1)?.occurrenceId) throw new Error("invalid history occurrence page");
85872
- const parents = (await git3(["rev-list", "--parents", "--max-count=8192", atCommit])).trim().split("\n").filter(Boolean).map((row) => {
86155
+ const parents = page.occurrences.length ? (await git3(["rev-list", "--parents", "--max-count=8192", atCommit])).trim().split("\n").filter(Boolean).map((row) => {
85873
86156
  const [commit, ...parents2] = row.trim().split(/\s+/);
85874
86157
  return { commit, parents: parents2 };
85875
- });
85876
- const reverse = buildReverseSymbolMap({ store, projectId, salt });
86158
+ }) : [];
86159
+ const reverse = page.occurrences.length ? await buildReverseSymbolMap({ store, projectId, salt }) : /* @__PURE__ */ new Map();
85877
86160
  const ignore2 = loadRepoIgnore(root, profile.ignoreExtra ?? []);
85878
86161
  const providers = defaultProviders();
85879
86162
  const parsedFiles = /* @__PURE__ */ new Map();