@inerrata-corporation/errata 2.0.13-dev.2998 → 2.0.13-dev.3054

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
@@ -15488,6 +15488,370 @@ var init_zod = __esm({
15488
15488
  }
15489
15489
  });
15490
15490
 
15491
+ // ../../packages/shared/src/history-text.ts
15492
+ function looksLikeSecretValue(raw2, quoted) {
15493
+ if (raw2.includes("${") || raw2.includes("{{") || raw2.includes("%(")) return false;
15494
+ if (raw2.startsWith("[redacted:")) return false;
15495
+ if (quoted) return raw2.length > 0;
15496
+ const lower = raw2.toLowerCase();
15497
+ if (ASSIGNMENT_STOPWORDS.has(lower)) return false;
15498
+ if (!/^[A-Za-z0-9+/=_-]{8,}$/.test(raw2)) return false;
15499
+ if (/\d/.test(raw2)) return true;
15500
+ if (raw2.length >= 12) return true;
15501
+ return /[a-z]/.test(raw2) && /[A-Z]/.test(raw2);
15502
+ }
15503
+ function applyRule(text, rule, removed) {
15504
+ let count = 0;
15505
+ const out2 = text.replace(rule.re, (...args2) => {
15506
+ const m = args2.slice(0, -2);
15507
+ const r = rule.replace(m);
15508
+ if (r === null) return m[0];
15509
+ const marker = r.indexOf(REDACTED_CREDENTIAL);
15510
+ if (removed && marker >= 0) {
15511
+ const prefix = r.slice(0, marker), suffix = r.slice(marker + REDACTED_CREDENTIAL.length);
15512
+ if (m[0].startsWith(prefix) && m[0].endsWith(suffix)) {
15513
+ const literal2 = m[0].slice(prefix.length, m[0].length - suffix.length);
15514
+ if (literal2 && !literal2.includes("[redacted:")) removed.add(literal2);
15515
+ }
15516
+ }
15517
+ count += 1;
15518
+ return r;
15519
+ });
15520
+ return { text: out2, count };
15521
+ }
15522
+ function scrubHistoryText(text) {
15523
+ return scrubText(text);
15524
+ }
15525
+ function scrubText(text, removed) {
15526
+ let out2 = text;
15527
+ let credentials = 0;
15528
+ for (const rule of CREDENTIAL_RULES) {
15529
+ const r = applyRule(out2, rule, removed);
15530
+ out2 = r.text;
15531
+ credentials += r.count;
15532
+ }
15533
+ let emails = 0;
15534
+ out2 = out2.replace(EMAIL_RE, () => {
15535
+ emails += 1;
15536
+ return REDACTED_EMAIL;
15537
+ });
15538
+ return { text: out2, credentials, emails };
15539
+ }
15540
+ function scrubHistoryValue(value) {
15541
+ const removed = /* @__PURE__ */ new Set();
15542
+ const visit = (input) => {
15543
+ if (typeof input === "string") return scrubText(input, removed).text;
15544
+ if (Array.isArray(input)) return input.map(visit);
15545
+ if (input && typeof input === "object") return Object.fromEntries(Object.entries(input).map(([key, item]) => {
15546
+ const field = key.toLowerCase().replace(/[^a-z0-9]/g, "");
15547
+ const credentialField = /^(?:token|credential|credentials|pwd|passwd|passphrase)$/.test(field) || /(?:password|secret|secretkey|apikey|accesskey|privatekey|authkey|accesstoken|refreshtoken)$/.test(field);
15548
+ if (typeof item === "string" && credentialField) {
15549
+ const assignment = `${key}: ${JSON.stringify(item)}`;
15550
+ if (scrubText(assignment, removed).credentials > 0) return [key, REDACTED_CREDENTIAL];
15551
+ }
15552
+ return [key, visit(item)];
15553
+ }));
15554
+ return input;
15555
+ };
15556
+ const safe = visit(value);
15557
+ if (!removed.size) return safe;
15558
+ const escaped = [...removed].sort((a, b) => b.length - a.length).map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
15559
+ const literals = new RegExp(`(?<![\\p{L}\\p{N}_])(?:${escaped.join("|")})(?![\\p{L}\\p{N}_])`, "gu");
15560
+ const clear = (input) => typeof input === "string" ? input.replace(literals, REDACTED_CREDENTIAL) : Array.isArray(input) ? input.map(clear) : input && typeof input === "object" ? Object.fromEntries(Object.entries(input).map(([k, v]) => [k, clear(v)])) : input;
15561
+ return clear(safe);
15562
+ }
15563
+ var HISTORY_SCRUBBER_VERSION, REDACTED_CREDENTIAL, REDACTED_EMAIL, cred, ASSIGNMENT_STOPWORDS, CREDENTIAL_RULES, EMAIL_RE;
15564
+ var init_history_text = __esm({
15565
+ "../../packages/shared/src/history-text.ts"() {
15566
+ "use strict";
15567
+ HISTORY_SCRUBBER_VERSION = "history-scrub-3";
15568
+ REDACTED_CREDENTIAL = "[redacted:credential]";
15569
+ REDACTED_EMAIL = "[redacted:email]";
15570
+ cred = () => REDACTED_CREDENTIAL;
15571
+ ASSIGNMENT_STOPWORDS = /* @__PURE__ */ new Set([
15572
+ "string",
15573
+ "boolean",
15574
+ "number",
15575
+ "required",
15576
+ "optional",
15577
+ "undefined",
15578
+ "unknown",
15579
+ "function",
15580
+ "process",
15581
+ "request",
15582
+ "response",
15583
+ "password",
15584
+ "secret",
15585
+ "token",
15586
+ "tokens",
15587
+ "credential",
15588
+ "credentials",
15589
+ "bearer",
15590
+ "basic",
15591
+ "example",
15592
+ "redacted",
15593
+ "nullable",
15594
+ "readonly",
15595
+ "override",
15596
+ "abstract",
15597
+ "interface",
15598
+ "constant",
15599
+ "variable",
15600
+ "property",
15601
+ "parameter",
15602
+ "argument",
15603
+ "callback",
15604
+ "promise",
15605
+ "settings",
15606
+ "options",
15607
+ "default",
15608
+ "disabled",
15609
+ "enabled",
15610
+ "internal",
15611
+ "external",
15612
+ "protected",
15613
+ "provided",
15614
+ "resolved",
15615
+ "rejected",
15616
+ "expected",
15617
+ "received",
15618
+ "salt",
15619
+ "hash",
15620
+ "hashed",
15621
+ "hashing",
15622
+ "authentication",
15623
+ "field",
15624
+ "format"
15625
+ ]);
15626
+ CREDENTIAL_RULES = [
15627
+ // Human-written seed/reset notes omit assignment punctuation: password `…`
15628
+ // or password (…). Preserve names of fields and hash components.
15629
+ {
15630
+ re: /\b((?:password|passwd|passphrase|pwd)\s+)(["'`(])([^\s"'`,;)}\]]+)(["'`)])/gi,
15631
+ replace: (m) => !ASSIGNMENT_STOPWORDS.has((m[3] ?? "").toLowerCase()) && looksLikeSecretValue(m[3] ?? "", true) ? `${m[1]}${m[2]}${REDACTED_CREDENTIAL}${m[4]}` : null
15632
+ },
15633
+ // "with SeedValue42! credentials" and "with SeedValue42!/seeded account".
15634
+ // Require mixed-case letters plus a digit, or a password-like special char;
15635
+ // ordinary phrases such as "with test credentials" remain useful prose.
15636
+ {
15637
+ re: /\b(with\s+)(["'`]?)([A-Za-z0-9][A-Za-z0-9!@#$%^&*._+=?-]{7,})(["'`]?)(?=\s+credentials\b|\s*\/\s*seeded\b)/gi,
15638
+ replace: (m) => /[a-z]/.test(m[3]) && /[A-Z]/.test(m[3]) && /[0-9!@#$%^&*]/.test(m[3]) ? `${m[1]}${m[2]}${REDACTED_CREDENTIAL}${m[4]}` : null
15639
+ },
15640
+ // Userinfo in connection URLs must be removed before the email rule runs.
15641
+ {
15642
+ re: /\b([a-z][a-z0-9+.-]*:\/\/)([^\s/@:]+):([^\s/@]+)@/gi,
15643
+ replace: (m) => m[2] === "[redacted" && m[3] === "credential]" ? null : `${m[1]}${REDACTED_CREDENTIAL}@`
15644
+ },
15645
+ // Seeded login prose commonly writes email/password without an assignment.
15646
+ {
15647
+ re: /(\b[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+|\[redacted:email\])(\s*\/\s*)(["'\x60]?)([^\s"'\x60,;)}\]]+)/gi,
15648
+ replace: (m) => m[4]?.startsWith("[redacted:") ? null : `${m[1]}${m[2]}${m[3]}${REDACTED_CREDENTIAL}`
15649
+ },
15650
+ // Explicit natural-language passwords, including weak seeded values.
15651
+ {
15652
+ re: /\b((?:password|passwd|passphrase|pwd)\s+(?:is|was|of)\s+)(["'\x60]?)([^\s"'\x60,;)}\]]+)/gi,
15653
+ replace: (m) => {
15654
+ const value = m[3] ?? "";
15655
+ if (ASSIGNMENT_STOPWORDS.has(value.toLowerCase()) || value.startsWith("[redacted:") || /\$\{|\{\{|process\./.test(value)) return null;
15656
+ return `${m[1]}${m[2]}${REDACTED_CREDENTIAL}`;
15657
+ }
15658
+ },
15659
+ // Private-key blocks, including ones a diff prefixes with `+`/`-` per line
15660
+ // and ones the diff context cut before the END line.
15661
+ {
15662
+ re: /-----BEGIN [A-Z ]*PRIVATE KEY(?: BLOCK)?-----(?:[ \t]*\r?\n[+\- ]?[A-Za-z0-9+/=]{16,}[ \t]*)*(?:[ \t]*\r?\n[+\- ]?-----END [A-Z ]*PRIVATE KEY(?: BLOCK)?-----)?/g,
15663
+ replace: cred
15664
+ },
15665
+ { re: /\b(?:AKIA|ASIA)[0-9A-Z]{16,}\b/g, replace: cred },
15666
+ // AWS access key id
15667
+ { re: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, replace: cred },
15668
+ { re: /\bgh[pousr]_[A-Za-z0-9]{30,}\b/g, replace: cred },
15669
+ { re: /\bglpat-[A-Za-z0-9_-]{20,}\b/g, replace: cred },
15670
+ { re: /\bxox[abeprs]-[A-Za-z0-9-]{10,}\b/g, replace: cred },
15671
+ // Slack
15672
+ { re: /\bsk-(?:ant-)?[A-Za-z0-9_-]{20,}\b/g, replace: cred },
15673
+ // OpenAI / Anthropic
15674
+ { re: /\b(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{20,}\b/g, replace: cred },
15675
+ // Stripe
15676
+ { re: /\brnd_[A-Za-z0-9]{20,}\b/g, replace: cred },
15677
+ // Render
15678
+ { re: /\bnpm_[A-Za-z0-9]{36}\b/g, replace: cred },
15679
+ { re: /\bAIza[0-9A-Za-z_-]{35}\b/g, replace: cred },
15680
+ // Google API key
15681
+ { re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, replace: cred },
15682
+ // JWT
15683
+ { re: /(?<![A-Za-z0-9])[A-Za-z0-9]{84}(?![A-Za-z0-9])/g, replace: cred },
15684
+ // Azure-shaped
15685
+ {
15686
+ re: /\b(Bearer|Basic|Token)(\s+)([A-Za-z0-9._~+/=-]{16,})/g,
15687
+ replace: (m) => `${m[1]}${m[2]}${REDACTED_CREDENTIAL}`
15688
+ },
15689
+ // Assignments after key-ish names: `password=...`, `API_KEY: "..."`,
15690
+ // `"client_secret": "..."`, `AWS_SECRET_ACCESS_KEY=...`, 32+ hex/base64 after
15691
+ // any of them. The value heuristic keeps `token: string` and
15692
+ // `password = process.env.X` intact.
15693
+ {
15694
+ re: /((?:[A-Za-z0-9_.-]*?)(?:password|passwd|passphrase|pwd|secret|token|api[_-]?key|apikey|access[_-]?key|private[_-]?key|auth[_-]?key|credential)(?:[A-Za-z0-9_-]*))(["']?\s*(?:=>|=|:)\s*)(["'`]?)([^\s"'`,;)}\]]+)/gi,
15695
+ replace: (m) => {
15696
+ const value = m[4] ?? "";
15697
+ const quote = m[3] ?? "";
15698
+ if (m[1]?.toLowerCase() === "credentials" && /^(include|omit|same-origin)$/.test(value)) return null;
15699
+ if (!looksLikeSecretValue(value, quote.length > 0)) return null;
15700
+ return `${m[1]}${m[2]}${quote}${REDACTED_CREDENTIAL}`;
15701
+ }
15702
+ }
15703
+ ];
15704
+ EMAIL_RE = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+\b/g;
15705
+ }
15706
+ });
15707
+
15708
+ // ../../packages/shared/src/history-evidence.ts
15709
+ import { createHash as createHash2 } from "node:crypto";
15710
+ function historyTextDigest(text) {
15711
+ return createHash2("sha256").update(text).digest("hex");
15712
+ }
15713
+ function historySourceBlocks(input) {
15714
+ const blocks = [];
15715
+ const add = (kind, text, commitSha, pathToken) => {
15716
+ const safe = scrubHistoryText(text.replace(/\r\n?/g, "\n")).text.trim();
15717
+ if (safe && safe !== "[truncated]") blocks.push({ id: `b${blocks.length}`, kind, commitSha, pathToken, text: safe, digest: historyTextDigest(safe) });
15718
+ };
15719
+ const narrative2 = scrubHistoryText(input.narrative.replace(/\r\n?/g, "\n")).text;
15720
+ if (input.narrativeSources?.length) {
15721
+ for (const source of input.narrativeSources) {
15722
+ const safe = scrubHistoryText(source.text.replace(/\r\n?/g, "\n")).text.trim();
15723
+ if (safe && narrative2.includes(safe)) add("narrative", safe, source.commitSha, null);
15724
+ }
15725
+ } else {
15726
+ add("narrative", narrative2, input.isMerge ? null : input.commitSha, null);
15727
+ }
15728
+ let token = null;
15729
+ let hunk = [];
15730
+ const flush = () => {
15731
+ if (hunk.some((line) => /^[+-](?![+-]{2})/.test(line))) add("diff", hunk.join("\n"), input.commitSha, token);
15732
+ hunk = [];
15733
+ };
15734
+ for (const line of input.diff.replace(/\r\n?/g, "\n").split("\n")) {
15735
+ if (line.startsWith("diff --git ")) {
15736
+ flush();
15737
+ token = /^diff --git a\/(p\d+) b\/(p\d+)$/.exec(line)?.[2] ?? null;
15738
+ } else if (/^@@ /.test(line)) {
15739
+ flush();
15740
+ hunk.push(line);
15741
+ } else if (!/^(--- |\+\+\+ |index |new file |deleted file |similarity |rename |\[truncated\])/.test(line) && token && /^[ +\-\\]/.test(line)) {
15742
+ hunk.push(line);
15743
+ }
15744
+ }
15745
+ flush();
15746
+ return blocks;
15747
+ }
15748
+ function resolveHistoryEvidence(selection, blocks) {
15749
+ const block = blocks.find((b) => b.id === selection.blockId);
15750
+ if (!block) return null;
15751
+ const quote = selection.quote.replace(/\r\n?/g, "\n");
15752
+ if (scrubHistoryText(quote).text !== quote) return null;
15753
+ let start2 = block.text.indexOf(quote);
15754
+ if (selection.startByte !== void 0) {
15755
+ const prefix = Buffer.from(block.text, "utf8").subarray(0, selection.startByte).toString("utf8");
15756
+ if (Buffer.byteLength(prefix, "utf8") !== selection.startByte || !block.text.startsWith(prefix)) return null;
15757
+ start2 = prefix.length;
15758
+ if (!block.text.startsWith(quote, start2)) return null;
15759
+ } else if (start2 < 0 || block.text.indexOf(quote, start2 + 1) !== -1) return null;
15760
+ const firstLine = block.text.lastIndexOf("\n", start2 - 1) + 1;
15761
+ const selectedLines = block.text.slice(firstLine, start2 + quote.length).split("\n");
15762
+ if (block.kind === "diff" && selectedLines.every((l) => l.startsWith("@@"))) return null;
15763
+ const signs = new Set(selectedLines.filter((l) => !l.startsWith("@@")).map((l) => l[0]));
15764
+ const side = block.kind === "narrative" ? "narrative" : signs.has("+") && signs.has("-") ? "mixed" : signs.has("+") ? "after" : signs.has("-") ? "before" : "context";
15765
+ return {
15766
+ ...selection,
15767
+ quote,
15768
+ blockDigest: block.digest,
15769
+ startByte: Buffer.byteLength(block.text.slice(0, start2), "utf8"),
15770
+ endByte: Buffer.byteLength(block.text.slice(0, start2 + quote.length), "utf8"),
15771
+ sourceKind: block.kind,
15772
+ commitSha: block.commitSha,
15773
+ pathToken: block.pathToken,
15774
+ scrubberVersion: HISTORY_SCRUBBER_VERSION,
15775
+ side
15776
+ };
15777
+ }
15778
+ function checkHistoryCandidate(atom, blocks) {
15779
+ const evidence = atom.evidence.map((e) => resolveHistoryEvidence(e, blocks));
15780
+ if (evidence.some((e) => e === null)) return { reason: "missing-evidence", evidence: [] };
15781
+ const refs = evidence;
15782
+ if (!refs.some((e) => e.supports === "statement") || atom.rationale !== null && !refs.some((e) => e.supports === "rationale") || atom.conditions.some((_, i2) => !refs.some((e) => e.supports === "condition" && e.conditionIndex === i2)) || refs.some((e) => e.supports === "relation" || (e.supports === "condition" ? e.conditionIndex === null || e.conditionIndex >= atom.conditions.length : e.conditionIndex !== null))) {
15783
+ return { reason: "missing-field-evidence", evidence: [] };
15784
+ }
15785
+ if (atom.label === "Claim" !== (atom.claimKind !== null)) return { reason: "invalid-kind", evidence: [] };
15786
+ if (atom.anchorTokens.some((token) => !blocks.some((b) => b.kind === "diff" && b.pathToken === token)) || atom.label !== "Claim" && atom.anchorTokens.length === 0) return { reason: "invalid-anchor", evidence: [] };
15787
+ return { reason: "accepted", evidence: refs };
15788
+ }
15789
+ var HISTORY_EVIDENCE_SCHEMA_VERSION, HistoryAnchorSourceSchema, HistorySymbolAnchorSchema, HistoryEvidenceSelectionSchema, HistoryCandidateSchema, HistoryRelationCandidateSchema, HistoryCandidateBatchSchema, HistoryEvidenceRefSchema;
15790
+ var init_history_evidence = __esm({
15791
+ "../../packages/shared/src/history-evidence.ts"() {
15792
+ "use strict";
15793
+ init_zod();
15794
+ init_history_text();
15795
+ HISTORY_EVIDENCE_SCHEMA_VERSION = "history-evidence-1";
15796
+ HistoryAnchorSourceSchema = external_exports.strictObject({
15797
+ sourceCommitSha: external_exports.string().regex(/^[a-f0-9]{40,64}$/),
15798
+ commitSha: external_exports.string().regex(/^[a-f0-9]{40,64}$/),
15799
+ side: external_exports.enum(["before", "after"]),
15800
+ blockDigest: external_exports.string().regex(/^[a-f0-9]{64}$/),
15801
+ startByte: external_exports.number().int().nonnegative(),
15802
+ endByte: external_exports.number().int().positive(),
15803
+ bodyHash: external_exports.string().regex(/^[a-f0-9]{8,64}$/).optional()
15804
+ }).refine((v) => v.endByte > v.startByte);
15805
+ HistorySymbolAnchorSchema = external_exports.object({
15806
+ symbolId: external_exports.string().regex(/^sym_[a-f0-9]{8,64}$/),
15807
+ kind: external_exports.string().max(40),
15808
+ /** Legacy association, never a source-revision or live validation. */
15809
+ contentHash: external_exports.string().regex(/^[a-f0-9]{8,64}$/).optional(),
15810
+ historical: HistoryAnchorSourceSchema.optional()
15811
+ });
15812
+ HistoryEvidenceSelectionSchema = external_exports.strictObject({
15813
+ blockId: external_exports.string().min(1).max(80),
15814
+ quote: external_exports.string().min(3).max(1600),
15815
+ supports: external_exports.enum(["statement", "condition", "rationale", "relation"]),
15816
+ conditionIndex: external_exports.number().int().min(0).max(7).nullable(),
15817
+ /** Optional exact location disambiguates repeated source lines. */
15818
+ startByte: external_exports.number().int().nonnegative().optional()
15819
+ });
15820
+ HistoryCandidateSchema = external_exports.strictObject({
15821
+ tempId: external_exports.string().regex(/^a[0-9]{1,3}$/),
15822
+ label: external_exports.enum(["Problem", "Solution", "RootCause", "Claim"]),
15823
+ statement: external_exports.string().min(10).max(2e3),
15824
+ claimKind: external_exports.enum(["snapshot", "decision", "constraint"]).nullable(),
15825
+ qualifier: external_exports.enum(["instance", "project"]),
15826
+ conditions: external_exports.array(external_exports.string().min(1).max(300)).max(8),
15827
+ rationale: external_exports.string().min(1).max(1e3).nullable(),
15828
+ evidence: external_exports.array(HistoryEvidenceSelectionSchema).min(1).max(12),
15829
+ anchorTokens: external_exports.array(external_exports.string().regex(/^p[0-9]+$/)).max(8)
15830
+ });
15831
+ HistoryRelationCandidateSchema = external_exports.strictObject({
15832
+ tempId: external_exports.string().regex(/^r[0-9]{1,3}$/),
15833
+ fromTempId: external_exports.string().regex(/^a[0-9]{1,3}$/),
15834
+ toTempId: external_exports.string().regex(/^a[0-9]{1,3}$/),
15835
+ meaning: external_exports.enum(["resolves", "mitigates", "caused-by"]),
15836
+ evidence: external_exports.array(HistoryEvidenceSelectionSchema).min(1).max(8)
15837
+ });
15838
+ HistoryCandidateBatchSchema = external_exports.strictObject({
15839
+ atoms: external_exports.array(HistoryCandidateSchema).max(32),
15840
+ relations: external_exports.array(HistoryRelationCandidateSchema).max(32)
15841
+ });
15842
+ HistoryEvidenceRefSchema = HistoryEvidenceSelectionSchema.extend({
15843
+ blockDigest: external_exports.string().regex(/^[a-f0-9]{64}$/),
15844
+ startByte: external_exports.number().int().nonnegative(),
15845
+ endByte: external_exports.number().int().nonnegative(),
15846
+ sourceKind: external_exports.enum(["narrative", "diff"]),
15847
+ commitSha: external_exports.string().regex(/^[a-f0-9]{40,64}$/).nullable(),
15848
+ pathToken: external_exports.string().regex(/^p[0-9]+$/).nullable(),
15849
+ scrubberVersion: external_exports.string().max(80),
15850
+ side: external_exports.enum(["before", "after", "context", "mixed", "narrative"])
15851
+ });
15852
+ }
15853
+ });
15854
+
15491
15855
  // ../../packages/shared/src/edge-rules.ts
15492
15856
  function isValidEdge(from, type, to) {
15493
15857
  const rule = EDGE_RULES[type];
@@ -15526,7 +15890,9 @@ var init_edge_rules = __esm({
15526
15890
  // ── Security spine (v1 taxonomy.ts SECURITY_FORWARD_ONLY commentary) ──
15527
15891
  TARGETS: { from: ["Exploit"], to: ["Vulnerability"] },
15528
15892
  ENABLES: { from: ["AntiPattern"], to: ["Vulnerability"] },
15529
- MITIGATES: { from: ["Solution"], to: ["Vulnerability"] },
15893
+ // Partial remedies also apply to project Problems/RootCauses. Direction stays
15894
+ // remedy -> ailment; a mitigation must never claim complete SOLVED_BY status.
15895
+ MITIGATES: { from: ["Solution"], to: ["Vulnerability", "Problem", "RootCause"] },
15530
15896
  PRESENTS: { from: ["Solution"], to: ["Problem", "Vulnerability"] },
15531
15897
  // SOLVED_BY sources: Vulnerability (v1 comment) + Problem/RootCause
15532
15898
  // (upstream design-problem.ts + triage.ts mint both).
@@ -15804,6 +16170,7 @@ var init_wire = __esm({
15804
16170
  "../../packages/shared/src/wire.ts"() {
15805
16171
  "use strict";
15806
16172
  init_zod();
16173
+ init_history_evidence();
15807
16174
  init_castalia();
15808
16175
  init_edge_rules();
15809
16176
  init_symbol_intent();
@@ -15930,6 +16297,12 @@ var init_wire = __esm({
15930
16297
  attrs: external_exports.object({
15931
16298
  confidence: external_exports.number().min(0).max(1).optional(),
15932
16299
  extractedBy: external_exports.enum(INGEST_EXTRACTION_SOURCES).optional(),
16300
+ sourceSupport: external_exports.literal("supported").optional(),
16301
+ historicalAnchors: external_exports.array(HistoryAnchorSourceSchema).max(160).optional(),
16302
+ evidence: external_exports.array(HistoryEvidenceRefSchema).max(12).optional(),
16303
+ verificationEvidence: external_exports.array(HistoryEvidenceRefSchema).max(12).optional(),
16304
+ verifierBuild: external_exports.string().max(80).optional(),
16305
+ unitDigest: external_exports.string().regex(/^[a-f0-9]{64}$/).optional(),
15933
16306
  /** Triage route per-context counts (T3 induction). Carried as structured data
15934
16307
  * here; stored JSON-encoded on the Neo4j rel (`perContextJson`). */
15935
16308
  perContext: external_exports.record(external_exports.string(), RouteContextCountWireSchema).optional(),
@@ -16740,6 +17113,178 @@ var init_manifests = __esm({
16740
17113
  }
16741
17114
  });
16742
17115
 
17116
+ // ../../packages/shared/src/history-quality.ts
17117
+ var init_history_quality = __esm({
17118
+ "../../packages/shared/src/history-quality.ts"() {
17119
+ "use strict";
17120
+ }
17121
+ });
17122
+
17123
+ // ../../packages/shared/src/history-read.ts
17124
+ function historyReadFields(source, attrsJson) {
17125
+ const attrs = unpackHistoryAttrs(typeof attrsJson === "string" ? { attrsJson } : {});
17126
+ const policy = historyReadPolicy({ extractionSource: source, attrs });
17127
+ if (!policy.history) return {};
17128
+ const quality = attrs["historyQuality"];
17129
+ return {
17130
+ historyNotice: policy.notice,
17131
+ ...quality && typeof quality === "object" && !Array.isArray(quality) ? { historyQuality: quality } : {}
17132
+ };
17133
+ }
17134
+ function unpackHistoryAttrs(attrs = {}) {
17135
+ let nested = {};
17136
+ if (typeof attrs["attrsJson"] === "string") {
17137
+ try {
17138
+ const parsed = JSON.parse(attrs["attrsJson"]);
17139
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) nested = parsed;
17140
+ } catch {
17141
+ }
17142
+ }
17143
+ return { ...nested, ...attrs };
17144
+ }
17145
+ function historyReadPolicy(node2, options = {}) {
17146
+ const attrs = unpackHistoryAttrs(node2.attrs);
17147
+ if (attrs["source"] === "cloud" && typeof attrs["extractionSource"] !== "string" && attrs["historyQuality"] == null) {
17148
+ return { allow: options.mode !== "passive", history: false, notice: "Cloud provenance is missing; refresh this cached result before using it as a standing prior." };
17149
+ }
17150
+ const isHistory = node2.extractionSource === "history-mined" || attrs["extractionSource"] === "history-mined" || attrs["historyQuality"] != null;
17151
+ if (!isHistory) return { allow: true, history: false, notice: null };
17152
+ const raw2 = attrs["historyQuality"];
17153
+ const q = raw2 && typeof raw2 === "object" ? raw2 : {};
17154
+ const source = q["sourceSupport"] ?? "unreviewed";
17155
+ const applicability = q["applicability"] ?? "unknown";
17156
+ const current = options.atCommit !== void 0 && q["evaluatedAtCommit"] === options.atCommit;
17157
+ const invalid = source === "contradicted" || source === "quarantined";
17158
+ const passive = options.mode === "passive";
17159
+ const validation = node2.validationSource ?? attrs["validationSource"];
17160
+ const independentlyValidated = validation === "human-explicit" || validation === "human-implicit" || validation === "agent-observed";
17161
+ const allow = !invalid && (!passive || source === "supported" && current && applicability === "applicable" && independentlyValidated);
17162
+ const at = typeof attrs["commitSha"] === "string" ? ` at ${attrs["commitSha"].slice(0, 12)}` : "";
17163
+ return {
17164
+ allow,
17165
+ history: true,
17166
+ notice: `History-derived${at}; source ${String(source)}; applicability ${current ? String(applicability) : "unknown at this checkout"}; ${independentlyValidated ? "separate validation recorded" : "not independently validated"}.`
17167
+ };
17168
+ }
17169
+ var init_history_read = __esm({
17170
+ "../../packages/shared/src/history-read.ts"() {
17171
+ "use strict";
17172
+ }
17173
+ });
17174
+
17175
+ // ../../packages/shared/src/history-temporal.ts
17176
+ async function evaluateHistoryApplicability(intervals, atCommit, isAncestor) {
17177
+ let superseded = false;
17178
+ let historical = false;
17179
+ let uncertain = false;
17180
+ for (const interval of intervals) {
17181
+ const [begun, covered, coherent] = await Promise.all([
17182
+ isAncestor(interval.fromCommit, atCommit),
17183
+ isAncestor(atCommit, interval.throughCommit),
17184
+ isAncestor(interval.fromCommit, interval.throughCommit)
17185
+ ]);
17186
+ if (begun === null || covered === null || coherent === null) {
17187
+ uncertain = true;
17188
+ continue;
17189
+ }
17190
+ if (!begun || !covered || !coherent) continue;
17191
+ if (interval.untilCommit) {
17192
+ const [ended, validEnd] = await Promise.all([isAncestor(interval.untilCommit, atCommit), isAncestor(interval.fromCommit, interval.untilCommit)]);
17193
+ if (ended === null || validEnd === null || !validEnd) {
17194
+ uncertain = true;
17195
+ continue;
17196
+ }
17197
+ if (ended) {
17198
+ if (interval.supersededByOccurrence) superseded = true;
17199
+ else historical = true;
17200
+ continue;
17201
+ }
17202
+ }
17203
+ return "applicable";
17204
+ }
17205
+ return uncertain ? "unknown" : superseded ? "superseded" : historical ? "historical" : "unknown";
17206
+ }
17207
+ var init_history_temporal = __esm({
17208
+ "../../packages/shared/src/history-temporal.ts"() {
17209
+ "use strict";
17210
+ }
17211
+ });
17212
+
17213
+ // ../../packages/shared/src/history-revision.ts
17214
+ function historyDag(records) {
17215
+ const graph = new Map(records.map((r) => [r.commit, r.parents]));
17216
+ if (graph.size !== records.length) throw new Error("duplicate Git commit in history proof");
17217
+ const incoming = new Map([...graph].map(([id, parents]) => [id, parents.filter((p) => graph.has(p)).length]));
17218
+ const children = /* @__PURE__ */ new Map();
17219
+ for (const [id, parents] of graph) for (const p of parents) if (graph.has(p)) children.set(p, [...children.get(p) ?? [], id]);
17220
+ const ready = [...incoming].filter(([, n]) => n === 0).map(([id]) => id);
17221
+ let visited = 0;
17222
+ while (ready.length) {
17223
+ const id = ready.pop();
17224
+ visited++;
17225
+ for (const child of children.get(id) ?? []) {
17226
+ const n = incoming.get(child) - 1;
17227
+ incoming.set(child, n);
17228
+ if (n === 0) ready.push(child);
17229
+ }
17230
+ }
17231
+ if (visited !== graph.size) throw new Error("cyclic history proof");
17232
+ return (a, b) => {
17233
+ if (!graph.has(a) || !graph.has(b)) return null;
17234
+ const pending = [b], seen = /* @__PURE__ */ new Set();
17235
+ let missing = false;
17236
+ while (pending.length) {
17237
+ const id = pending.pop();
17238
+ if (id === a) return true;
17239
+ if (seen.has(id)) continue;
17240
+ seen.add(id);
17241
+ const parents = graph.get(id);
17242
+ if (!parents) missing = true;
17243
+ else pending.push(...parents);
17244
+ }
17245
+ return missing ? null : false;
17246
+ };
17247
+ }
17248
+ function occurrenceSourceCommit(o) {
17249
+ const sha2 = o.quality["commitSha"];
17250
+ return typeof sha2 === "string" && Sha.safeParse(sha2).success ? sha2 : null;
17251
+ }
17252
+ function historySemanticKey(projectId, o) {
17253
+ const q = o.quality;
17254
+ const anchors = q["historicalAnchors"];
17255
+ if (q["sourceSupport"] !== "supported" || !anchors?.length || anchors.some((a) => !a.symbolId)) return null;
17256
+ return digest([projectId, o.label, o.statement, q["claimKind"] ?? null, q["qualifier"], q["conditions"], q["rationale"], [...new Set(anchors.map((a) => a.symbolId))].sort()]);
17257
+ }
17258
+ function preservedHistoryOccurrence(o, observed, atCommit, ancestor) {
17259
+ const q = o.quality, source = occurrenceSourceCommit(o);
17260
+ if (!source || ancestor(source, atCommit) !== true || q["sourceSupport"] !== "supported" || !observed) return false;
17261
+ const parsed = external_exports.array(HistoryEvidenceRefSchema).safeParse(q["evidence"]);
17262
+ if (!parsed.success || !parsed.data.length || parsed.data.some((e) => e.sourceKind !== "diff" || !["after", "context"].includes(e.side))) return false;
17263
+ const anchors = q["historicalAnchors"];
17264
+ if (!anchors?.length || anchors.some((a) => a.side !== "after" || !a.bodyHash || !observed.symbols.some((s) => s.symbolId === a.symbolId && s.bodyHash === a.bodyHash))) return false;
17265
+ return parsed.data.every((e) => anchors.some((a) => a.blockDigest === e.blockDigest && a.startByte <= e.startByte && a.endByte >= e.endByte));
17266
+ }
17267
+ var Sha, HistoryRevisionRequestSchema;
17268
+ var init_history_revision = __esm({
17269
+ "../../packages/shared/src/history-revision.ts"() {
17270
+ "use strict";
17271
+ init_zod();
17272
+ init_hash();
17273
+ init_history_evidence();
17274
+ Sha = external_exports.string().regex(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/);
17275
+ HistoryRevisionRequestSchema = external_exports.strictObject({
17276
+ atCommit: Sha,
17277
+ parents: external_exports.array(external_exports.strictObject({ commit: Sha, parents: external_exports.array(Sha).max(32) })).max(8192),
17278
+ observations: external_exports.array(external_exports.strictObject({
17279
+ occurrenceId: external_exports.string().min(1).max(100),
17280
+ symbols: external_exports.array(external_exports.strictObject({ symbolId: external_exports.string().regex(/^sym_[a-f0-9]{8,64}$/), bodyHash: external_exports.string().regex(/^[a-f0-9]{8,64}$/) })).max(32),
17281
+ /** Scrubbed, tokenized net change from this occurrence's source to atCommit. */
17282
+ diff: external_exports.string().max(16e3).optional()
17283
+ })).max(50)
17284
+ });
17285
+ }
17286
+ });
17287
+
16743
17288
  // ../../packages/shared/src/nlp/canonicalize.ts
16744
17289
  function canonicalize(value) {
16745
17290
  return value.normalize("NFC").trim().toLowerCase().replace(/\s+/g, " ");
@@ -16809,6 +17354,16 @@ __export(src_exports, {
16809
17354
  FILE_ANCHOR_QNAME: () => FILE_ANCHOR_QNAME,
16810
17355
  GIT_EDGES: () => GIT_EDGES,
16811
17356
  GRAPH_EVENT_VOCABULARIES: () => GRAPH_EVENT_VOCABULARIES,
17357
+ HISTORY_EVIDENCE_SCHEMA_VERSION: () => HISTORY_EVIDENCE_SCHEMA_VERSION,
17358
+ HISTORY_SCRUBBER_VERSION: () => HISTORY_SCRUBBER_VERSION,
17359
+ HistoryAnchorSourceSchema: () => HistoryAnchorSourceSchema,
17360
+ HistoryCandidateBatchSchema: () => HistoryCandidateBatchSchema,
17361
+ HistoryCandidateSchema: () => HistoryCandidateSchema,
17362
+ HistoryEvidenceRefSchema: () => HistoryEvidenceRefSchema,
17363
+ HistoryEvidenceSelectionSchema: () => HistoryEvidenceSelectionSchema,
17364
+ HistoryRelationCandidateSchema: () => HistoryRelationCandidateSchema,
17365
+ HistoryRevisionRequestSchema: () => HistoryRevisionRequestSchema,
17366
+ HistorySymbolAnchorSchema: () => HistorySymbolAnchorSchema,
16812
17367
  IDENTITY_EDGES: () => IDENTITY_EDGES,
16813
17368
  INGEST_EXTRACTION_SOURCES: () => INGEST_EXTRACTION_SOURCES,
16814
17369
  INGEST_SOURCES: () => INGEST_SOURCES,
@@ -16824,6 +17379,8 @@ __export(src_exports, {
16824
17379
  PROJECT_ONLY_EDGE_TYPES: () => PROJECT_ONLY_EDGE_TYPES,
16825
17380
  PROJECT_ONLY_NODE_LABELS: () => PROJECT_ONLY_NODE_LABELS,
16826
17381
  QUARANTINE_EXTRACTION_SOURCE_PREFIX: () => QUARANTINE_EXTRACTION_SOURCE_PREFIX,
17382
+ REDACTED_CREDENTIAL: () => REDACTED_CREDENTIAL,
17383
+ REDACTED_EMAIL: () => REDACTED_EMAIL,
16827
17384
  RESOLUTION_EDGES: () => RESOLUTION_EDGES,
16828
17385
  RE_LEADING_ARTICLE: () => RE_LEADING_ARTICLE,
16829
17386
  ROUTING_REASONS: () => ROUTING_REASONS,
@@ -16854,6 +17411,7 @@ __export(src_exports, {
16854
17411
  canonicalizeContext: () => canonicalizeContext,
16855
17412
  canonicalizeError: () => canonicalizeError,
16856
17413
  canonicalizeToken: () => canonicalizeToken,
17414
+ checkHistoryCandidate: () => checkHistoryCandidate,
16857
17415
  claimStructure: () => claimStructure,
16858
17416
  conceptBag: () => conceptBag,
16859
17417
  conceptTokens: () => conceptTokens,
@@ -16863,8 +17421,15 @@ __export(src_exports, {
16863
17421
  detectStance: () => detectStance,
16864
17422
  digest: () => digest,
16865
17423
  errorSignatureEqual: () => errorSignatureEqual,
17424
+ evaluateHistoryApplicability: () => evaluateHistoryApplicability,
16866
17425
  extractIdentifierTokens: () => extractIdentifierTokens,
16867
17426
  genericCanonicalId: () => genericCanonicalId,
17427
+ historyDag: () => historyDag,
17428
+ historyReadFields: () => historyReadFields,
17429
+ historyReadPolicy: () => historyReadPolicy,
17430
+ historySemanticKey: () => historySemanticKey,
17431
+ historySourceBlocks: () => historySourceBlocks,
17432
+ historyTextDigest: () => historyTextDigest,
16868
17433
  identityId: () => identityId,
16869
17434
  isAnchorLabelKind: () => isAnchorLabelKind,
16870
17435
  isCausalProtected: () => isCausalProtected,
@@ -16891,6 +17456,7 @@ __export(src_exports, {
16891
17456
  normalizeSymbolPath: () => normalizeSymbolPath,
16892
17457
  normalizeToolName: () => normalizeToolName,
16893
17458
  npmPurl: () => npmPurl,
17459
+ occurrenceSourceCommit: () => occurrenceSourceCommit,
16894
17460
  operatingSystemCanonicalId: () => operatingSystemCanonicalId,
16895
17461
  osNameForPlatform: () => osNameForPlatform,
16896
17462
  packageCanonicalId: () => packageCanonicalId,
@@ -16902,14 +17468,18 @@ __export(src_exports, {
16902
17468
  parsePackageRef: () => parsePackageRef,
16903
17469
  parsePnpmLock: () => parsePnpmLock,
16904
17470
  prefilterEntities: () => prefilterEntities,
17471
+ preservedHistoryOccurrence: () => preservedHistoryOccurrence,
16905
17472
  problemIdFromError: () => problemIdFromError,
16906
17473
  projectSaltedNodeId: () => projectSaltedNodeId,
16907
17474
  projectSymbolId: () => projectSymbolId,
16908
17475
  provenanceBand: () => provenanceBand,
16909
17476
  resolveCanonical: () => resolveCanonical,
16910
17477
  resolveCanonicalId: () => resolveCanonicalId,
17478
+ resolveHistoryEvidence: () => resolveHistoryEvidence,
16911
17479
  resolveUnambiguousCanonicalId: () => resolveUnambiguousCanonicalId,
16912
17480
  retrievalTokens: () => retrievalTokens,
17481
+ scrubHistoryText: () => scrubHistoryText,
17482
+ scrubHistoryValue: () => scrubHistoryValue,
16913
17483
  sha256: () => sha256,
16914
17484
  sha256Short: () => sha256Short,
16915
17485
  sidecarDropBucket: () => sidecarDropBucket,
@@ -16922,6 +17492,7 @@ __export(src_exports, {
16922
17492
  toCloudAttrs: () => toCloudAttrs,
16923
17493
  toolCanonicalId: () => toolCanonicalId,
16924
17494
  triageDiscriminatorAttrs: () => triageDiscriminatorAttrs,
17495
+ unpackHistoryAttrs: () => unpackHistoryAttrs,
16925
17496
  unregisteredGraphEventKeys: () => unregisteredGraphEventKeys,
16926
17497
  validateCastaliaPayload: () => validateCastaliaPayload,
16927
17498
  versionlessPurl: () => versionlessPurl,
@@ -16940,6 +17511,11 @@ var init_src = __esm({
16940
17511
  init_project_symbol();
16941
17512
  init_lockfile();
16942
17513
  init_manifests();
17514
+ init_history_quality();
17515
+ init_history_read();
17516
+ init_history_temporal();
17517
+ init_history_revision();
17518
+ init_history_evidence();
16943
17519
  init_hash();
16944
17520
  init_error_signature();
16945
17521
  init_canonicalize();
@@ -16949,6 +17525,7 @@ var init_src = __esm({
16949
17525
  init_predicate();
16950
17526
  init_taxonomy();
16951
17527
  init_triage_canon();
17528
+ init_history_text();
16952
17529
  }
16953
17530
  });
16954
17531
 
@@ -21307,6 +21884,15 @@ function percolate(l1, l2, opts) {
21307
21884
  for (const label of PERCOLATING_LABELS) {
21308
21885
  for (const n of l1.findNodesByLabel(label)) {
21309
21886
  if (n.label === "Problem" && n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) continue;
21887
+ if (n.attrs["source"] === "cloud") {
21888
+ const raw2 = l2.getMeta?.(`history-correction:${String(n.attrs["cloudNodeId"] ?? n.id)}`);
21889
+ const correction = raw2 ? JSON.parse(raw2) : null;
21890
+ const support2 = n.attrs["historyQuality"]?.["sourceSupport"];
21891
+ if (correction?.action === "quarantine" || correction?.sourceSupport === "contradicted" || support2 === "quarantined" || support2 === "contradicted") {
21892
+ result.nodesSkipped++;
21893
+ continue;
21894
+ }
21895
+ }
21310
21896
  present.add(n.id);
21311
21897
  const existing = l2.getNode(n.id);
21312
21898
  if (!existing) {
@@ -24222,6 +24808,7 @@ var init_src5 = __esm({
24222
24808
 
24223
24809
  // ../../packages/context-writer/src/render.ts
24224
24810
  function isPassiveInjectable(node2) {
24811
+ if (!historyReadPolicy(node2, { mode: "passive" }).allow) return false;
24225
24812
  if (node2.label === "Pattern" || node2.label === "Technique" || node2.label === "AntiPattern") {
24226
24813
  return true;
24227
24814
  }
@@ -24231,7 +24818,7 @@ function isPassiveInjectable(node2) {
24231
24818
  return false;
24232
24819
  }
24233
24820
  function buildSnapshot(opts) {
24234
- const problems = opts.store.findNodesByLabel("Problem").filter((p) => p.attrs["mergedInto"] === void 0);
24821
+ const problems = opts.store.findNodesByLabel("Problem").filter((p) => p.attrs["mergedInto"] === void 0 && historyReadPolicy(p, { mode: "passive" }).allow);
24235
24822
  const stillOpen = problems.filter((p) => p.attrs["resolvedAt"] == null);
24236
24823
  const allProblems = stillOpen.filter((p) => !isConstraintProblem(p));
24237
24824
  allProblems.sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt);
@@ -24261,10 +24848,11 @@ function buildSnapshot(opts) {
24261
24848
  ...opts.store.findNodesByLabel("Pattern"),
24262
24849
  ...opts.store.findNodesByLabel("Technique"),
24263
24850
  ...opts.store.findNodesByLabel("AntiPattern")
24264
- ].sort((a, b) => b.pageRank - a.pageRank).slice(0, 10);
24851
+ ].filter((n) => historyReadPolicy(n, { mode: "passive" }).allow).sort((a, b) => b.pageRank - a.pageRank).slice(0, 10);
24265
24852
  const workingFile = opts.workingFile ? sliceForFile(opts.store, opts.workingFile) : void 0;
24266
24853
  const byEpisode = /* @__PURE__ */ new Map();
24267
24854
  for (const sol of opts.store.findNodesByLabel("Solution")) {
24855
+ if (!historyReadPolicy(sol, { mode: "passive" }).allow) continue;
24268
24856
  if (sol.attrs["enrichmentPending"] !== true) continue;
24269
24857
  const ep = sol.attrs["episodeId"] ?? sol.id;
24270
24858
  const list = byEpisode.get(ep) ?? [];
@@ -24374,6 +24962,14 @@ function sliceForFile(store, relPath) {
24374
24962
  if (externalRefs.length >= 20) break;
24375
24963
  }
24376
24964
  const fp = priorsForFile(store, relPath);
24965
+ if (fp) {
24966
+ const allowed = (n) => historyReadPolicy(n, { mode: "passive" }).allow;
24967
+ fp.openProblems = fp.openProblems.filter(allowed);
24968
+ fp.constraints = fp.constraints.filter(allowed);
24969
+ fp.resolvedProblems = fp.resolvedProblems.filter(allowed);
24970
+ fp.related = fp.related.filter(allowed);
24971
+ for (const [id, solutions] of fp.solutionsByProblem) fp.solutionsByProblem.set(id, solutions.filter(allowed));
24972
+ }
24377
24973
  const priors = fp ? {
24378
24974
  openProblems: fp.openProblems.slice(0, 3).map((p) => ({
24379
24975
  id: p.id,
@@ -24846,6 +25442,7 @@ var init_render = __esm({
24846
25442
  "use strict";
24847
25443
  init_src5();
24848
25444
  init_src2();
25445
+ init_src();
24849
25446
  RECALL_FIRST_HEADER = "## errata \u2014 recall before you work";
24850
25447
  RECALL_FIRST_BODY = "Before investigating any bug or error, your FIRST tool call must be `mcp__errata__errata_search` with a few words describing the problem. This machine's knowledge graph records previously solved problems \u2014 the answer may already be recorded. Read what it returns before touching any files.";
24851
25448
  RECALL_FIRST_BLOCK = `${RECALL_FIRST_HEADER}
@@ -24993,7 +25590,7 @@ function selectDurableMemory(store, profile, opts = {}) {
24993
25590
  const minContributors = opts.minContributors ?? 1;
24994
25591
  const wsLangs = new Set(profile.languages.map((l) => l.toLowerCase()));
24995
25592
  return store.findNodesByLabel("Claim").filter(
24996
- (n) => Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) === ABSTRACTION_LEVEL.PRINCIPLE && n.attrs["provisional"] === false && contributorsOf(n) >= minContributors && // Lang-relevance: universal principles (no scope.lang) bleed everywhere; a
25593
+ (n) => historyReadPolicy(n, { mode: "passive" }).allow && Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) === ABSTRACTION_LEVEL.PRINCIPLE && n.attrs["provisional"] === false && contributorsOf(n) >= minContributors && // Lang-relevance: universal principles (no scope.lang) bleed everywhere; a
24997
25594
  // language-scoped principle only bleeds into repos that USE that language —
24998
25595
  // so a Python principle never lands a dead `**/*.py` rule in a TS repo.
24999
25596
  (scopeLangOf(n) == null || wsLangs.has(scopeLangOf(n)))
@@ -25017,6 +25614,7 @@ var init_select_durable_memory = __esm({
25017
25614
  "../../packages/context-writer/src/select-durable-memory.ts"() {
25018
25615
  "use strict";
25019
25616
  init_src2();
25617
+ init_src();
25020
25618
  num2 = (v, d = 0) => typeof v === "number" && Number.isFinite(v) ? v : d;
25021
25619
  LANG_GLOBS = {
25022
25620
  typescript: ["**/*.ts", "**/*.tsx"],
@@ -25854,7 +26452,7 @@ var init_client = __esm({
25854
26452
  label: n.label,
25855
26453
  description: n.description,
25856
26454
  extractionConfidence: typeof n.attrs["score"] === "number" ? clamp012(n.attrs["score"]) : 0.5,
25857
- extractionSource: "agent-observed",
26455
+ extractionSource: INGEST_EXTRACTION_SOURCES.includes(n.attrs["extractionSource"]) ? n.attrs["extractionSource"] : "cold-start-seed",
25858
26456
  embedding: [],
25859
26457
  cumulativeSurprise: 0,
25860
26458
  peakSurprise: 0,
@@ -25866,7 +26464,7 @@ var init_client = __esm({
25866
26464
  isLandmark: false,
25867
26465
  community: null,
25868
26466
  stability: "stable",
25869
- attrs: { ...n.attrs, source: "cloud", cloudNodeId: n.nodeId }
26467
+ attrs: { ...unpackHistoryAttrs(n.attrs), source: "cloud", cloudNodeId: n.nodeId }
25870
26468
  }));
25871
26469
  const edges = res.edges.map((e) => ({
25872
26470
  id: digest({ from: e.fromCanonicalId, type: e.type, to: e.toCanonicalId }),
@@ -25915,6 +26513,17 @@ var init_client = __esm({
25915
26513
  * daemon filters the page against its own checkout, and nothing about which
25916
26514
  * symbols it holds is sent. Same membership gate as priors.
25917
26515
  */
26516
+ /** Content-free invalidations, independent of code-symbol/anchor availability. */
26517
+ async getProjectHistoryCorrections(projectId, since, limit = 200) {
26518
+ return this.json("GET", `/v2/projects/${encodeURIComponent(projectId)}/anchors?correctionsOnly=1&correctionSince=${String(since)}&limit=${String(limit)}`);
26519
+ }
26520
+ async getHistoryOccurrences(projectId, atCommit, after) {
26521
+ const query = new URLSearchParams({ atCommit, limit: "50", ...after ? { after } : {} });
26522
+ return this.json("GET", `/v2/projects/${encodeURIComponent(projectId)}/history/occurrences?${query}`);
26523
+ }
26524
+ async reconcileHistory(projectId, proof) {
26525
+ return this.json("POST", `/v2/projects/${encodeURIComponent(projectId)}/history/reconcile`, proof);
26526
+ }
25918
26527
  async getProjectAnchors(projectId, since, limit = 200) {
25919
26528
  const res = await this.json(
25920
26529
  "GET",
@@ -26066,13 +26675,13 @@ var init_client = __esm({
26066
26675
  });
26067
26676
 
26068
26677
  // ../../packages/cloud-client/src/pkce.ts
26069
- import { createHash as createHash2, randomBytes } from "node:crypto";
26678
+ import { createHash as createHash3, randomBytes } from "node:crypto";
26070
26679
  function base64url3(buf) {
26071
26680
  return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
26072
26681
  }
26073
26682
  function generatePkce() {
26074
26683
  const verifier = base64url3(randomBytes(32));
26075
- const challenge = base64url3(createHash2("sha256").update(verifier).digest());
26684
+ const challenge = base64url3(createHash3("sha256").update(verifier).digest());
26076
26685
  return { verifier, challenge, method: "S256" };
26077
26686
  }
26078
26687
  function randomState() {
@@ -29368,14 +29977,14 @@ var init_git_remote = __esm({
29368
29977
 
29369
29978
  // src/profile.ts
29370
29979
  import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
29371
- import { createHash as createHash3 } from "node:crypto";
29980
+ import { createHash as createHash4 } from "node:crypto";
29372
29981
  import { join as join7 } from "node:path";
29373
29982
  function workspaceId(root) {
29374
- return "wp_" + createHash3("sha256").update(root).digest("hex").slice(0, 12);
29983
+ return "wp_" + createHash4("sha256").update(root).digest("hex").slice(0, 12);
29375
29984
  }
29376
29985
  function sessionOriginKey(sessionId) {
29377
29986
  if (!sessionId) return void 0;
29378
- return "ws_" + createHash3("sha256").update(sessionId).digest("hex").slice(0, 12);
29987
+ return "ws_" + createHash4("sha256").update(sessionId).digest("hex").slice(0, 12);
29379
29988
  }
29380
29989
  function refreshRepoLocator(root, profile) {
29381
29990
  const detected = detectRepoLocator(root, profile.repoRemote);
@@ -29813,7 +30422,7 @@ function scrub(input) {
29813
30422
  return `<api-key:${id}>`;
29814
30423
  });
29815
30424
  }
29816
- text = text.replace(EMAIL_RE, (m) => {
30425
+ text = text.replace(EMAIL_RE2, (m) => {
29817
30426
  const id = sha256Short(m, 6);
29818
30427
  bump("email", `<email:${id}>`);
29819
30428
  return `<email:${id}>`;
@@ -29864,8 +30473,8 @@ function detectViolations(input) {
29864
30473
  if (p.re.test(input)) violations.add("api-key");
29865
30474
  p.re.lastIndex = 0;
29866
30475
  }
29867
- if (EMAIL_RE.test(input)) violations.add("email");
29868
- EMAIL_RE.lastIndex = 0;
30476
+ if (EMAIL_RE2.test(input)) violations.add("email");
30477
+ EMAIL_RE2.lastIndex = 0;
29869
30478
  if (WIN_PATH_RE.test(input)) violations.add("windows-path");
29870
30479
  WIN_PATH_RE.lastIndex = 0;
29871
30480
  if (WIN_UNC_RE.test(input)) violations.add("windows-path");
@@ -29876,7 +30485,7 @@ function detectViolations(input) {
29876
30485
  IPV4_RE.lastIndex = 0;
29877
30486
  return [...violations];
29878
30487
  }
29879
- var API_KEY_PATTERNS, EMAIL_RE, IPV4_RE, IPV6_RE, WIN_PATH_RE, WIN_UNC_RE, POSIX_USER_PATH_RE, LONG_HASH_RE, UUID_RE;
30488
+ var API_KEY_PATTERNS, EMAIL_RE2, IPV4_RE, IPV6_RE, WIN_PATH_RE, WIN_UNC_RE, POSIX_USER_PATH_RE, LONG_HASH_RE, UUID_RE;
29880
30489
  var init_scrub = __esm({
29881
30490
  "../../packages/canonical/src/scrub.ts"() {
29882
30491
  "use strict";
@@ -29890,7 +30499,7 @@ var init_scrub = __esm({
29890
30499
  { name: "stripe-key", re: /\b(sk|pk)_(test|live)_[0-9a-zA-Z]{24,}\b/g },
29891
30500
  { name: "bearer", re: /\bBearer\s+[A-Za-z0-9_\-\.]{16,}\b/gi }
29892
30501
  ];
29893
- EMAIL_RE = /\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b/g;
30502
+ EMAIL_RE2 = /\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b/g;
29894
30503
  IPV4_RE = /\b(?:\d{1,3}\.){3}\d{1,3}\b/g;
29895
30504
  IPV6_RE = /\b(?:[A-Fa-f0-9]{1,4}:){2,7}[A-Fa-f0-9:]{1,4}\b/g;
29896
30505
  WIN_PATH_RE = /\b[A-Za-z]:[\\/](?![\\/])(?:[^\\/\s"']+[\\/])*[^\\/\s"']*/g;
@@ -32622,6 +33231,7 @@ var init_nightly = __esm({
32622
33231
  });
32623
33232
 
32624
33233
  // ../../packages/generalizer/src/embed-code.ts
33234
+ import { createHash as createHash5 } from "node:crypto";
32625
33235
  function embedCodeNodes(store, opts = {}) {
32626
33236
  return embedNodesByLabels(store, CODE_LABELS, opts);
32627
33237
  }
@@ -32630,9 +33240,15 @@ function embedSemanticNodes(store, opts = {}) {
32630
33240
  }
32631
33241
  async function ensureNodeEmbedded(store, nodeId, opts = {}) {
32632
33242
  const node2 = store.getNode(nodeId);
32633
- if (!node2 || node2.embedding.length > 0) return false;
33243
+ if (!node2) return false;
33244
+ if (!eligibleForEmbedding(store, node2)) {
33245
+ clearWithdrawnVector(store, node2.id);
33246
+ return false;
33247
+ }
33248
+ if (node2.embedding.length > 0) return false;
32634
33249
  const text = buildText(store, node2);
32635
33250
  if (!text) return false;
33251
+ const before = embeddingSnapshot(node2, text);
32636
33252
  let result;
32637
33253
  try {
32638
33254
  if (opts.mode === "hash") {
@@ -32646,12 +33262,41 @@ async function ensureNodeEmbedded(store, nodeId, opts = {}) {
32646
33262
  } catch {
32647
33263
  return false;
32648
33264
  }
32649
- store.updateNode(nodeId, {
33265
+ return store.transaction(() => writeIfUnchanged(store, before, result));
33266
+ }
33267
+ function embeddingState(node2) {
33268
+ return createHash5("sha256").update(JSON.stringify([node2.attrs["embeddingVersion"] ?? null, node2.embedding])).digest("hex");
33269
+ }
33270
+ function embeddingSnapshot(node2, text) {
33271
+ return { id: node2.id, text, validTo: node2.validTo, vectorState: embeddingState(node2) };
33272
+ }
33273
+ function writeIfUnchanged(store, before, result) {
33274
+ const current = store.getNode(before.id);
33275
+ if (!current || !eligibleForEmbedding(store, current) || buildText(store, current) !== before.text || current.validTo !== before.validTo || embeddingState(current) !== before.vectorState)
33276
+ return false;
33277
+ store.updateNode(current.id, {
32650
33278
  embedding: result.vector,
32651
- attrs: { ...node2.attrs, embeddingVersion: result.version }
33279
+ attrs: { ...current.attrs, embeddingVersion: result.version }
32652
33280
  });
32653
33281
  return true;
32654
33282
  }
33283
+ function eligibleForEmbedding(store, node2) {
33284
+ const cloudId = node2.attrs["cloudNodeId"];
33285
+ const raw2 = node2.attrs["source"] === "cloud" ? store.getMeta?.(`history-correction:${typeof cloudId === "string" ? cloudId : node2.id}`) : null;
33286
+ const correction = raw2 ? JSON.parse(raw2) : null;
33287
+ const withdrawn = correction && Number(correction.seq) >= Number(node2.attrs["historyCorrectionSeq"] ?? 0) && (correction.action === "quarantine" || correction.sourceSupport === "contradicted");
33288
+ return !withdrawn && historyReadPolicy(node2).allow && !node2.attrs["mergedInto"] && !node2.attrs["fusedInto"];
33289
+ }
33290
+ function clearWithdrawnVector(store, nodeId) {
33291
+ store.transaction(() => {
33292
+ const node2 = store.getNode(nodeId);
33293
+ if (!node2 || eligibleForEmbedding(store, node2)) return;
33294
+ if (node2.embedding.length || node2.attrs["embeddingVersion"] !== void 0) {
33295
+ const { embeddingVersion: _version, ...attrs } = node2.attrs;
33296
+ store.mergeNode({ ...node2, embedding: [], attrs });
33297
+ }
33298
+ });
33299
+ }
32655
33300
  async function embedNodesByLabels(store, labels, opts = {}) {
32656
33301
  const started2 = Date.now();
32657
33302
  const report = {
@@ -32666,8 +33311,14 @@ async function embedNodesByLabels(store, labels, opts = {}) {
32666
33311
  const currentVersion = hashOnly ? EMBEDDING_VERSION : (await embedBatchWithModelOrHash(["probe"]))[0]?.version ?? "unknown";
32667
33312
  const todo = [];
32668
33313
  for (const label of labels) {
32669
- for (const node2 of store.findNodesByLabel(label)) {
33314
+ for (const node2 of store.findNodesByLabel(
33315
+ label
33316
+ )) {
32670
33317
  report.scanned++;
33318
+ if (!eligibleForEmbedding(store, node2)) {
33319
+ clearWithdrawnVector(store, node2.id);
33320
+ continue;
33321
+ }
32671
33322
  const existingVersion = node2.attrs["embeddingVersion"] ?? null;
32672
33323
  if (existingVersion) {
32673
33324
  report.versionsSeen[existingVersion] = (report.versionsSeen[existingVersion] ?? 0) + 1;
@@ -32682,7 +33333,7 @@ async function embedNodesByLabels(store, labels, opts = {}) {
32682
33333
  }
32683
33334
  const text = buildText(store, node2);
32684
33335
  if (!text) continue;
32685
- todo.push({ id: node2.id, text });
33336
+ todo.push(embeddingSnapshot(node2, text));
32686
33337
  }
32687
33338
  }
32688
33339
  for (let i2 = 0; i2 < todo.length; i2 += BATCH_SIZE) {
@@ -32699,17 +33350,12 @@ async function embedNodesByLabels(store, labels, opts = {}) {
32699
33350
  }
32700
33351
  store.transaction(() => {
32701
33352
  for (let j = 0; j < batch.length; j++) {
32702
- const node2 = store.getNode(batch[j].id);
32703
- if (!node2) continue;
32704
- const newAttrs = {
32705
- ...node2.attrs,
32706
- embeddingVersion: results[j].version
32707
- };
32708
- store.updateNode(batch[j].id, {
32709
- embedding: results[j].vector,
32710
- attrs: newAttrs
32711
- });
32712
- report.embedded++;
33353
+ const result = results[j];
33354
+ if (!result) {
33355
+ report.failed++;
33356
+ continue;
33357
+ }
33358
+ if (writeIfUnchanged(store, batch[j], result)) report.embedded++;
32713
33359
  }
32714
33360
  });
32715
33361
  if (opts.progress) opts.progress(report.embedded, todo.length);
@@ -32738,6 +33384,7 @@ var CODE_LABELS, SEMANTIC_LABELS, BATCH_SIZE;
32738
33384
  var init_embed_code = __esm({
32739
33385
  "../../packages/generalizer/src/embed-code.ts"() {
32740
33386
  "use strict";
33387
+ init_src();
32741
33388
  init_src3();
32742
33389
  CODE_LABELS = ["Function", "Method", "Class", "Const"];
32743
33390
  SEMANTIC_LABELS = ["Problem", "RootCause", "Solution", "Pattern", "Claim"];
@@ -32901,7 +33548,7 @@ var init_src10 = __esm({
32901
33548
  });
32902
33549
 
32903
33550
  // ../../packages/indexer/src/parse-cache.ts
32904
- import { createHash as createHash4 } from "node:crypto";
33551
+ import { createHash as createHash6 } from "node:crypto";
32905
33552
  import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync10, readdirSync as readdirSync2, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync9 } from "node:fs";
32906
33553
  import { homedir as homedir3 } from "node:os";
32907
33554
  import { join as join11 } from "node:path";
@@ -32909,7 +33556,7 @@ function parseCacheDir() {
32909
33556
  return process.env["ERRATA_PARSE_CACHE"] ?? join11(homedir3(), ".errata", "parse-cache");
32910
33557
  }
32911
33558
  function parseCacheKey(source, providerId) {
32912
- return createHash4("sha256").update(`${PARSE_CACHE_VERSION}:${providerId}:${source}`).digest("hex");
33559
+ return createHash6("sha256").update(`${PARSE_CACHE_VERSION}:${providerId}:${source}`).digest("hex");
32913
33560
  }
32914
33561
  function entryPath(dir, key) {
32915
33562
  return join11(dir, key.slice(0, 2), `${key.slice(2)}.json`);
@@ -33207,7 +33854,7 @@ var init_identity3 = __esm({
33207
33854
  import { appendFileSync, readFileSync as readFileSync11, statSync as statSync3 } from "node:fs";
33208
33855
  import { readdir } from "node:fs/promises";
33209
33856
  import { extname, join as join12, relative as relative2, sep } from "node:path";
33210
- import { createHash as createHash5 } from "node:crypto";
33857
+ import { createHash as createHash7 } from "node:crypto";
33211
33858
  import { execFileSync } from "node:child_process";
33212
33859
  function nowTs() {
33213
33860
  return indexNow ?? Date.now();
@@ -33216,7 +33863,7 @@ function fingerprintOf(signature, bodyHash) {
33216
33863
  return `${signature ?? ""}|${bodyHash ?? ""}`;
33217
33864
  }
33218
33865
  function sha(s) {
33219
- return createHash5("sha256").update(s).digest("hex").slice(0, 16);
33866
+ return createHash7("sha256").update(s).digest("hex").slice(0, 16);
33220
33867
  }
33221
33868
  function fileNodeId(workspaceId2, relPath) {
33222
33869
  return `file_${sha(workspaceId2 + ":" + relPath.replace(/\\/g, "/"))}`;
@@ -33344,7 +33991,7 @@ async function incrementalReindex(store, rootPath, workspaceId2, changedAbsPaths
33344
33991
  for (const rel of [...changedRelPaths]) {
33345
33992
  let h;
33346
33993
  try {
33347
- h = createHash5("sha256").update(readFileSync11(join12(rootPath, rel))).digest("hex");
33994
+ h = createHash7("sha256").update(readFileSync11(join12(rootPath, rel))).digest("hex");
33348
33995
  } catch {
33349
33996
  continue;
33350
33997
  }
@@ -34267,7 +34914,7 @@ function upsertFile(store, id, f, workspaceId2, skip) {
34267
34914
  if (existing && fileUnchanged(existing.attrs, f, skip)) return;
34268
34915
  let contentHash;
34269
34916
  try {
34270
- contentHash = createHash5("sha256").update(readFileSync11(f.absPath)).digest("hex");
34917
+ contentHash = createHash7("sha256").update(readFileSync11(f.absPath)).digest("hex");
34271
34918
  } catch {
34272
34919
  }
34273
34920
  const carried = { ...existing?.attrs ?? {} };
@@ -38796,7 +39443,7 @@ var init_tree_sitter_loader = __esm({
38796
39443
  });
38797
39444
 
38798
39445
  // ../../packages/indexer/src/languages/typescript-treesitter.ts
38799
- import { createHash as createHash6 } from "node:crypto";
39446
+ import { createHash as createHash8 } from "node:crypto";
38800
39447
  function extractNode(node2, source, containerQname, containerKind) {
38801
39448
  switch (node2.type) {
38802
39449
  case "function_declaration":
@@ -39009,7 +39656,7 @@ function scopeRecord(kind, name2, qname, node2, body2, source) {
39009
39656
  const sig = header.replace(/\s+/g, " ").trim();
39010
39657
  if (sig) rec.signature = sig;
39011
39658
  const bodyText = source.slice(body2.startIndex, body2.endIndex);
39012
- rec.bodyHash = createHash6("sha256").update(bodyText).digest("hex").slice(0, 16);
39659
+ rec.bodyHash = createHash8("sha256").update(bodyText).digest("hex").slice(0, 16);
39013
39660
  rec.bodySimhash = toHex(simhash(bodyText));
39014
39661
  }
39015
39662
  }
@@ -39451,14 +40098,14 @@ var init_typescript_treesitter = __esm({
39451
40098
  });
39452
40099
 
39453
40100
  // ../../packages/indexer/src/languages/python-treesitter.ts
39454
- import { createHash as createHash7 } from "node:crypto";
40101
+ import { createHash as createHash9 } from "node:crypto";
39455
40102
  function sigAndHash(node2, body2, source) {
39456
40103
  if (!body2) return {};
39457
40104
  const out2 = {};
39458
40105
  const sig = source.slice(node2.startIndex, body2.startIndex).replace(/\s+/g, " ").trim();
39459
40106
  if (sig) out2.signature = sig;
39460
40107
  const bodyText = source.slice(body2.startIndex, body2.endIndex);
39461
- out2.bodyHash = createHash7("sha256").update(bodyText).digest("hex").slice(0, 16);
40108
+ out2.bodyHash = createHash9("sha256").update(bodyText).digest("hex").slice(0, 16);
39462
40109
  out2.bodySimhash = toHex(simhash(bodyText));
39463
40110
  return out2;
39464
40111
  }
@@ -39871,7 +40518,7 @@ var init_python_treesitter = __esm({
39871
40518
  });
39872
40519
 
39873
40520
  // ../../packages/indexer/src/languages/cpp-treesitter.ts
39874
- import { createHash as createHash8 } from "node:crypto";
40521
+ import { createHash as createHash10 } from "node:crypto";
39875
40522
  function extractNode3(node2, containerQname, containerIsClass, source) {
39876
40523
  switch (node2.type) {
39877
40524
  case "function_definition":
@@ -40055,7 +40702,7 @@ function sigAndHash2(node2, body2, source) {
40055
40702
  const sig = source.slice(node2.startIndex, body2.startIndex).replace(/\s+/g, " ").trim();
40056
40703
  if (sig) out2.signature = sig;
40057
40704
  const bodyText = source.slice(body2.startIndex, body2.endIndex);
40058
- out2.bodyHash = createHash8("sha256").update(bodyText).digest("hex").slice(0, 16);
40705
+ out2.bodyHash = createHash10("sha256").update(bodyText).digest("hex").slice(0, 16);
40059
40706
  out2.bodySimhash = toHex(simhash(bodyText));
40060
40707
  return out2;
40061
40708
  }
@@ -40216,7 +40863,7 @@ var init_cpp_treesitter = __esm({
40216
40863
  });
40217
40864
 
40218
40865
  // ../../packages/indexer/src/languages/go-treesitter.ts
40219
- import { createHash as createHash9 } from "node:crypto";
40866
+ import { createHash as createHash11 } from "node:crypto";
40220
40867
  function extractNode4(node2, containerQname, source) {
40221
40868
  switch (node2.type) {
40222
40869
  case "function_declaration": {
@@ -40378,7 +41025,7 @@ function sigAndHash3(node2, body2, source) {
40378
41025
  const sig = source.slice(node2.startIndex, body2.startIndex).replace(/\s+/g, " ").trim();
40379
41026
  if (sig) out2.signature = sig;
40380
41027
  const bodyText = source.slice(body2.startIndex, body2.endIndex);
40381
- out2.bodyHash = createHash9("sha256").update(bodyText).digest("hex").slice(0, 16);
41028
+ out2.bodyHash = createHash11("sha256").update(bodyText).digest("hex").slice(0, 16);
40382
41029
  out2.bodySimhash = toHex(simhash(bodyText));
40383
41030
  return out2;
40384
41031
  }
@@ -40522,7 +41169,7 @@ var init_go_treesitter = __esm({
40522
41169
  });
40523
41170
 
40524
41171
  // ../../packages/indexer/src/languages/rust-treesitter.ts
40525
- import { createHash as createHash10 } from "node:crypto";
41172
+ import { createHash as createHash12 } from "node:crypto";
40526
41173
  function extractNode5(node2, containerQname, containerIsClass, source) {
40527
41174
  switch (node2.type) {
40528
41175
  case "function_item":
@@ -40737,7 +41384,7 @@ function sigAndHash4(node2, body2, source) {
40737
41384
  const sig = source.slice(node2.startIndex, body2.startIndex).replace(/\s+/g, " ").trim();
40738
41385
  if (sig) out2.signature = sig;
40739
41386
  const bodyText = source.slice(body2.startIndex, body2.endIndex);
40740
- out2.bodyHash = createHash10("sha256").update(bodyText).digest("hex").slice(0, 16);
41387
+ out2.bodyHash = createHash12("sha256").update(bodyText).digest("hex").slice(0, 16);
40741
41388
  out2.bodySimhash = toHex(simhash(bodyText));
40742
41389
  return out2;
40743
41390
  }
@@ -40888,7 +41535,7 @@ var init_rust_treesitter = __esm({
40888
41535
  });
40889
41536
 
40890
41537
  // ../../packages/indexer/src/languages/ruby-treesitter.ts
40891
- import { createHash as createHash11 } from "node:crypto";
41538
+ import { createHash as createHash13 } from "node:crypto";
40892
41539
  function sigAndHash5(node2, body2, source) {
40893
41540
  if (!body2) {
40894
41541
  const sig2 = source.slice(node2.startIndex, node2.endIndex).replace(/\s+/g, " ").trim();
@@ -40898,7 +41545,7 @@ function sigAndHash5(node2, body2, source) {
40898
41545
  const sig = source.slice(node2.startIndex, body2.startIndex).replace(/\s+/g, " ").trim();
40899
41546
  if (sig) out2.signature = sig;
40900
41547
  const bodyText = source.slice(body2.startIndex, body2.endIndex);
40901
- out2.bodyHash = createHash11("sha256").update(bodyText).digest("hex").slice(0, 16);
41548
+ out2.bodyHash = createHash13("sha256").update(bodyText).digest("hex").slice(0, 16);
40902
41549
  out2.bodySimhash = toHex(simhash(bodyText));
40903
41550
  return out2;
40904
41551
  }
@@ -41244,7 +41891,7 @@ var init_ruby_treesitter = __esm({
41244
41891
  });
41245
41892
 
41246
41893
  // ../../packages/indexer/src/languages/csharp-treesitter.ts
41247
- import { createHash as createHash12 } from "node:crypto";
41894
+ import { createHash as createHash14 } from "node:crypto";
41248
41895
  function extractNode7(node2, containerQname, containerIsType, source, eof) {
41249
41896
  switch (node2.type) {
41250
41897
  case "method_declaration":
@@ -41446,7 +42093,7 @@ function sigAndHash6(node2, body2, source) {
41446
42093
  const sig = source.slice(node2.startIndex, body2.startIndex).replace(/\s+/g, " ").trim();
41447
42094
  if (sig) out2.signature = sig;
41448
42095
  const bodyText = source.slice(body2.startIndex, body2.endIndex);
41449
- out2.bodyHash = createHash12("sha256").update(bodyText).digest("hex").slice(0, 16);
42096
+ out2.bodyHash = createHash14("sha256").update(bodyText).digest("hex").slice(0, 16);
41450
42097
  out2.bodySimhash = toHex(simhash(bodyText));
41451
42098
  return out2;
41452
42099
  }
@@ -51362,10 +52009,10 @@ import {
51362
52009
  writeFileSync as writeFileSync11
51363
52010
  } from "node:fs";
51364
52011
  import { join as join16 } from "node:path";
51365
- import { createHash as createHash13 } from "node:crypto";
52012
+ import { createHash as createHash15 } from "node:crypto";
51366
52013
  function enqueueOutbox(paths, payload) {
51367
52014
  ensureDir(paths.outbox);
51368
- const id = createHash13("sha256").update(JSON.stringify(payload)).digest("hex").slice(0, 16);
52015
+ const id = createHash15("sha256").update(JSON.stringify(payload)).digest("hex").slice(0, 16);
51369
52016
  const file2 = join16(paths.outbox, `${Date.now()}-${id}.json`);
51370
52017
  writeFileSync11(file2, JSON.stringify(payload, null, 2), "utf8");
51371
52018
  return file2;
@@ -63247,7 +63894,7 @@ var require_mock_interceptor = __commonJS({
63247
63894
  var require_mock_client = __commonJS({
63248
63895
  "../../node_modules/.pnpm/undici@6.24.1/node_modules/undici/lib/mock/mock-client.js"(exports, module2) {
63249
63896
  "use strict";
63250
- var { promisify: promisify3 } = __require("node:util");
63897
+ var { promisify: promisify4 } = __require("node:util");
63251
63898
  var Client = require_client();
63252
63899
  var { buildMockDispatch } = require_mock_utils();
63253
63900
  var {
@@ -63287,7 +63934,7 @@ var require_mock_client = __commonJS({
63287
63934
  return new MockInterceptor(opts, this[kDispatches]);
63288
63935
  }
63289
63936
  async [kClose]() {
63290
- await promisify3(this[kOriginalClose])();
63937
+ await promisify4(this[kOriginalClose])();
63291
63938
  this[kConnected] = 0;
63292
63939
  this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
63293
63940
  }
@@ -63300,7 +63947,7 @@ var require_mock_client = __commonJS({
63300
63947
  var require_mock_pool = __commonJS({
63301
63948
  "../../node_modules/.pnpm/undici@6.24.1/node_modules/undici/lib/mock/mock-pool.js"(exports, module2) {
63302
63949
  "use strict";
63303
- var { promisify: promisify3 } = __require("node:util");
63950
+ var { promisify: promisify4 } = __require("node:util");
63304
63951
  var Pool = require_pool();
63305
63952
  var { buildMockDispatch } = require_mock_utils();
63306
63953
  var {
@@ -63340,7 +63987,7 @@ var require_mock_pool = __commonJS({
63340
63987
  return new MockInterceptor(opts, this[kDispatches]);
63341
63988
  }
63342
63989
  async [kClose]() {
63343
- await promisify3(this[kOriginalClose])();
63990
+ await promisify4(this[kOriginalClose])();
63344
63991
  this[kConnected] = 0;
63345
63992
  this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
63346
63993
  }
@@ -74104,7 +74751,7 @@ init_checkout_root();
74104
74751
  import { closeSync, existsSync as existsSync16, fstatSync, openSync, readdirSync as readdirSync6, readSync, realpathSync, statSync as statSync6 } from "node:fs";
74105
74752
  import { basename as basename5, delimiter, dirname as dirname10, join as join20, normalize as normalize3 } from "node:path";
74106
74753
  import { homedir as homedir4 } from "node:os";
74107
- import { createHash as createHash14 } from "node:crypto";
74754
+ import { createHash as createHash16 } from "node:crypto";
74108
74755
  function readFrom(path2, fromByte, maxBytes) {
74109
74756
  let fd;
74110
74757
  try {
@@ -74413,7 +75060,7 @@ function parseCodexAssistantTurns(raw2, skipFirstLineParseFailure = false, start
74413
75060
  turns.push({
74414
75061
  // Slice-local line numbers repeat on every Stop. Position distinguishes
74415
75062
  // identical messages; the digest distinguishes a replaced row at that position.
74416
- uuid: typeof payload["id"] === "string" && payload["id"].length > 0 ? payload["id"] : `codex-${lineByte}-${createHash14("sha256").update(line.trimEnd()).digest("hex")}`,
75063
+ uuid: typeof payload["id"] === "string" && payload["id"].length > 0 ? payload["id"] : `codex-${lineByte}-${createHash16("sha256").update(line.trimEnd()).digest("hex")}`,
74417
75064
  text: parts2.join("\n\n"),
74418
75065
  hostHarness: "codex"
74419
75066
  });
@@ -74662,7 +75309,7 @@ function geminiSessionRoots(env2 = process.env, home = homedir4()) {
74662
75309
  return [...roots];
74663
75310
  }
74664
75311
  function geminiProjectHash(cwd) {
74665
- return createHash14("sha256").update(cwd).digest("hex");
75312
+ return createHash16("sha256").update(cwd).digest("hex");
74666
75313
  }
74667
75314
  function geminiHashesFor(cwd) {
74668
75315
  const variants = /* @__PURE__ */ new Set([cwd, cwd.replace(/\/+$/, "")]);
@@ -77983,7 +78630,7 @@ var TelemetryRecorder = class {
77983
78630
  // src/file-activity.ts
77984
78631
  import { readFileSync as readFileSync22 } from "node:fs";
77985
78632
  import { join as join28 } from "node:path";
77986
- import { createHash as createHash15 } from "node:crypto";
78633
+ import { createHash as createHash17 } from "node:crypto";
77987
78634
 
77988
78635
  // ../../packages/privacy/src/patterns/credentials.ts
77989
78636
  var CREDENTIAL_PATTERNS = [
@@ -78732,6 +79379,7 @@ var openaiAgent = new import_undici.Agent({
78732
79379
  init_git_remote();
78733
79380
 
78734
79381
  // src/history-bundle.ts
79382
+ init_src();
78735
79383
  import { execFile as execFile2 } from "node:child_process";
78736
79384
  import { readFileSync as readFileSync21 } from "node:fs";
78737
79385
  import { join as join27 } from "node:path";
@@ -78740,8 +79388,6 @@ var FILE_LIST_CAP = 60;
78740
79388
  var DIFF_FILE_CAP = 40;
78741
79389
  var GENERATED_JSON_MAX_CHURN = 800;
78742
79390
  var TRUNCATION_MARKER = "\n[truncated]";
78743
- var REDACTED_CREDENTIAL = "[redacted:credential]";
78744
- var REDACTED_EMAIL = "[redacted:email]";
78745
79391
  var CHARS_PER_TOKEN = 3.5;
78746
79392
  function estimateTokens(text) {
78747
79393
  return Math.ceil(text.length / CHARS_PER_TOKEN);
@@ -78882,138 +79528,6 @@ function isSourceFile(f) {
78882
79528
  if (ext === "json" && f.adds + f.dels > GENERATED_JSON_MAX_CHURN) return false;
78883
79529
  return true;
78884
79530
  }
78885
- var cred = () => REDACTED_CREDENTIAL;
78886
- var ASSIGNMENT_STOPWORDS = /* @__PURE__ */ new Set([
78887
- "string",
78888
- "boolean",
78889
- "number",
78890
- "required",
78891
- "optional",
78892
- "undefined",
78893
- "unknown",
78894
- "function",
78895
- "process",
78896
- "request",
78897
- "response",
78898
- "password",
78899
- "secret",
78900
- "token",
78901
- "tokens",
78902
- "credential",
78903
- "credentials",
78904
- "bearer",
78905
- "basic",
78906
- "example",
78907
- "redacted",
78908
- "nullable",
78909
- "readonly",
78910
- "override",
78911
- "abstract",
78912
- "interface",
78913
- "constant",
78914
- "variable",
78915
- "property",
78916
- "parameter",
78917
- "argument",
78918
- "callback",
78919
- "promise",
78920
- "settings",
78921
- "options",
78922
- "default",
78923
- "disabled",
78924
- "enabled",
78925
- "internal",
78926
- "external",
78927
- "protected",
78928
- "provided",
78929
- "resolved",
78930
- "rejected",
78931
- "expected",
78932
- "received"
78933
- ]);
78934
- function looksLikeSecretValue(raw2, quoted) {
78935
- if (raw2.includes("${") || raw2.includes("{{") || raw2.includes("%(")) return false;
78936
- if (raw2.startsWith(REDACTED_CREDENTIAL) || raw2.startsWith(REDACTED_EMAIL)) return false;
78937
- const lower = raw2.toLowerCase();
78938
- if (ASSIGNMENT_STOPWORDS.has(lower)) return false;
78939
- if (quoted) return raw2.length >= 6;
78940
- if (!/^[A-Za-z0-9+/=_-]{8,}$/.test(raw2)) return false;
78941
- if (/\d/.test(raw2)) return true;
78942
- if (raw2.length >= 12) return true;
78943
- return /[a-z]/.test(raw2) && /[A-Z]/.test(raw2);
78944
- }
78945
- var CREDENTIAL_RULES = [
78946
- // Private-key blocks, including ones a diff prefixes with `+`/`-` per line
78947
- // and ones the diff context cut before the END line.
78948
- {
78949
- re: /-----BEGIN [A-Z ]*PRIVATE KEY(?: BLOCK)?-----(?:[ \t]*\r?\n[+\- ]?[A-Za-z0-9+/=]{16,}[ \t]*)*(?:[ \t]*\r?\n[+\- ]?-----END [A-Z ]*PRIVATE KEY(?: BLOCK)?-----)?/g,
78950
- replace: cred
78951
- },
78952
- { re: /\b(?:AKIA|ASIA)[0-9A-Z]{16,}\b/g, replace: cred },
78953
- // AWS access key id
78954
- { re: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, replace: cred },
78955
- { re: /\bgh[pousr]_[A-Za-z0-9]{30,}\b/g, replace: cred },
78956
- { re: /\bglpat-[A-Za-z0-9_-]{20,}\b/g, replace: cred },
78957
- { re: /\bxox[abeprs]-[A-Za-z0-9-]{10,}\b/g, replace: cred },
78958
- // Slack
78959
- { re: /\bsk-(?:ant-)?[A-Za-z0-9_-]{20,}\b/g, replace: cred },
78960
- // OpenAI / Anthropic
78961
- { re: /\b(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{20,}\b/g, replace: cred },
78962
- // Stripe
78963
- { re: /\brnd_[A-Za-z0-9]{20,}\b/g, replace: cred },
78964
- // Render
78965
- { re: /\bnpm_[A-Za-z0-9]{36}\b/g, replace: cred },
78966
- { re: /\bAIza[0-9A-Za-z_-]{35}\b/g, replace: cred },
78967
- // Google API key
78968
- { re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, replace: cred },
78969
- // JWT
78970
- { re: /(?<![A-Za-z0-9])[A-Za-z0-9]{84}(?![A-Za-z0-9])/g, replace: cred },
78971
- // Azure-shaped
78972
- {
78973
- re: /\b(Bearer|Basic|Token)(\s+)([A-Za-z0-9._~+/=-]{16,})/g,
78974
- replace: (m) => `${m[1]}${m[2]}${REDACTED_CREDENTIAL}`
78975
- },
78976
- // Assignments after key-ish names: `password=...`, `API_KEY: "..."`,
78977
- // `"client_secret": "..."`, `AWS_SECRET_ACCESS_KEY=...`, 32+ hex/base64 after
78978
- // any of them. The value heuristic keeps `token: string` and
78979
- // `password = process.env.X` intact.
78980
- {
78981
- re: /((?:[A-Za-z0-9_.-]*?)(?:password|passwd|passphrase|pwd|secret|token|api[_-]?key|apikey|access[_-]?key|private[_-]?key|auth[_-]?key|credential)(?:[A-Za-z0-9_-]*))(["']?\s*(?:=>|=|:)\s*)(["'`]?)([^\s"'`,;)}\]]+)/gi,
78982
- replace: (m) => {
78983
- const value = m[4] ?? "";
78984
- const quote = m[3] ?? "";
78985
- if (!looksLikeSecretValue(value, quote.length > 0)) return null;
78986
- return `${m[1]}${m[2]}${quote}${REDACTED_CREDENTIAL}`;
78987
- }
78988
- }
78989
- ];
78990
- var EMAIL_RE2 = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+\b/g;
78991
- function applyRule(text, rule) {
78992
- let count = 0;
78993
- const out2 = text.replace(rule.re, (...args2) => {
78994
- const m = args2.slice(0, -2);
78995
- const r = rule.replace(m);
78996
- if (r === null) return m[0];
78997
- count += 1;
78998
- return r;
78999
- });
79000
- return { text: out2, count };
79001
- }
79002
- function scrubText(text) {
79003
- let out2 = text;
79004
- let credentials = 0;
79005
- for (const rule of CREDENTIAL_RULES) {
79006
- const r = applyRule(out2, rule);
79007
- out2 = r.text;
79008
- credentials += r.count;
79009
- }
79010
- let emails = 0;
79011
- out2 = out2.replace(EMAIL_RE2, () => {
79012
- emails += 1;
79013
- return REDACTED_EMAIL;
79014
- });
79015
- return { text: out2, credentials, emails };
79016
- }
79017
79531
  var TRAILER_KEYS = [
79018
79532
  "signed-off-by",
79019
79533
  "co-authored-by",
@@ -79135,12 +79649,18 @@ async function buildUnitBundle(root, unit, opts = {}) {
79135
79649
  const rawDiff = await unitDiff(root, unit, diffPaths);
79136
79650
  for (const p of headerPaths(rawDiff)) tokens.token(p);
79137
79651
  const narrativeRaw = opts.narrative ?? [unit.subject.trim(), stripTrailers(unit.body)].filter((s) => s.length > 0).join("\n\n");
79138
- const n = scrubText(tokens.apply(stripTrailers(narrativeRaw)));
79139
- const fl = scrubText(tokens.apply(fileListRaw));
79140
- const d = scrubText(tokens.apply(rawDiff));
79141
- let narrative2 = n.text;
79142
- let fileList = fl.text;
79143
- let diff = d.text;
79652
+ const n = scrubHistoryText(tokens.apply(stripTrailers(narrativeRaw)));
79653
+ const fl = scrubHistoryText(tokens.apply(fileListRaw));
79654
+ const d = scrubHistoryText(tokens.apply(rawDiff));
79655
+ const safe = scrubHistoryValue({
79656
+ narrative: tokens.apply(stripTrailers(narrativeRaw)),
79657
+ fileList: tokens.apply(fileListRaw),
79658
+ diff: tokens.apply(rawDiff),
79659
+ narrativeSources: opts.narrativeSources?.map((source) => ({ commitSha: source.commitSha, text: tokens.apply(stripTrailers(source.text)).trim() }))
79660
+ });
79661
+ let narrative2 = safe.narrative;
79662
+ let fileList = safe.fileList;
79663
+ let diff = safe.diff;
79144
79664
  const separators = 2;
79145
79665
  let prose = estimateTokens(narrative2) + estimateTokens(fileList) + separators;
79146
79666
  if (prose > maxTokens) {
@@ -79159,6 +79679,7 @@ async function buildUnitBundle(root, unit, opts = {}) {
79159
79679
  return {
79160
79680
  sha: unit.sha,
79161
79681
  narrative: narrative2,
79682
+ ...safe.narrativeSources ? { narrativeSources: safe.narrativeSources.filter((source) => source.text.length > 0 && narrative2.includes(source.text)).slice(0, 64) } : {},
79162
79683
  fileList,
79163
79684
  diff,
79164
79685
  tokenEstimate: estimateTokens(narrative2) + estimateTokens(fileList) + estimateTokens(diff) + separators,
@@ -79281,7 +79802,7 @@ var FileActivityReporter = class {
79281
79802
  const current = this.options.binding();
79282
79803
  if (!identity || this.stopped || !current.enabled || current.projectId !== binding.projectId)
79283
79804
  return false;
79284
- const hash2 = createHash15("sha256").update(JSON.stringify([identity, source, at, clean, operation, branch, previousBranch])).digest("hex");
79805
+ const hash2 = createHash17("sha256").update(JSON.stringify([identity, source, at, clean, operation, branch, previousBranch])).digest("hex");
79285
79806
  const id = `${hash2.slice(0, 8)}-${hash2.slice(8, 12)}-5${hash2.slice(13, 16)}-8${hash2.slice(17, 20)}-${hash2.slice(20, 32)}`;
79286
79807
  if (!this.pending.some((item) => item.event.id === id))
79287
79808
  this.pending.push({
@@ -80338,7 +80859,7 @@ function createWatchBreaker(deps) {
80338
80859
  }
80339
80860
 
80340
80861
  // src/engine.ts
80341
- var DAEMON_VERSION = true ? "2.0.13-dev.2998" : "2.0.0-alpha.0";
80862
+ var DAEMON_VERSION = true ? "2.0.13-dev.3054" : "2.0.0-alpha.0";
80342
80863
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
80343
80864
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
80344
80865
  var GIT_OP_MUTE_MS = 4e3;
@@ -82797,10 +83318,10 @@ init_registry();
82797
83318
  init_identity2();
82798
83319
 
82799
83320
  // src/instance-receipts.ts
82800
- import { createHash as createHash16 } from "node:crypto";
83321
+ import { createHash as createHash18 } from "node:crypto";
82801
83322
  var FIELDS = ["contributedAtSeq", "cloudNodeId", "anchorsContributedDigest", "projectAnchorsContributedDigest", "semanticEdgesContributedDigest", "shipRefusedSinceSeq", "shipRefusedCount", "shipRefusedReason", "shipRefusedFingerprint"];
82802
83323
  function instanceDestinationKey(input) {
82803
- return createHash16("sha256").update(JSON.stringify([
83324
+ return createHash18("sha256").update(JSON.stringify([
82804
83325
  input.workspace,
82805
83326
  input.cloudUrl.replace(/\/+$/, ""),
82806
83327
  input.installationId ?? input.profileName ?? "default",
@@ -84282,6 +84803,69 @@ init_profile2();
84282
84803
  init_src();
84283
84804
  init_src11();
84284
84805
  init_profile2();
84806
+
84807
+ // src/history-corrections.ts
84808
+ init_profile2();
84809
+ function applyHistoryCorrections(store, page) {
84810
+ let changed = 0;
84811
+ for (const correction of page.changes) {
84812
+ const key = `history-correction:${correction.nodeId}`;
84813
+ const previous = store.getMeta?.(key);
84814
+ if (!previous || Number(JSON.parse(previous).seq) < correction.seq) store.setMeta?.(key, JSON.stringify(correction));
84815
+ const current = store.getNode(correction.canonicalId) ?? store.getNode(correction.nodeId);
84816
+ if (!current) continue;
84817
+ const privacy = correction.action === "quarantine";
84818
+ const copies = privacy ? store.nodeHistory(current.logicalId ?? current.id) : [current];
84819
+ for (const node2 of copies) {
84820
+ if (node2.attrs["source"] !== "cloud" || node2.attrs["cloudNodeId"] && node2.attrs["cloudNodeId"] !== correction.nodeId || Number(node2.attrs["historyCorrectionSeq"] ?? 0) >= correction.seq) continue;
84821
+ const quality = { sourceSupport: correction.sourceSupport ?? (privacy ? "quarantined" : "unreviewed"), applicability: "unknown" };
84822
+ store.mergeNode({
84823
+ ...node2,
84824
+ lastUpdatedAt: Date.now(),
84825
+ ...privacy ? { description: "History source withdrawn.", embedding: [], isLandmark: false } : {},
84826
+ attrs: {
84827
+ ...privacy ? {} : node2.attrs,
84828
+ source: "cloud",
84829
+ cloudNodeId: correction.nodeId,
84830
+ extractionSource: "history-mined",
84831
+ historyQuality: quality,
84832
+ historyCorrectionSeq: correction.seq
84833
+ }
84834
+ });
84835
+ if (privacy) for (const edge2 of [...store.inEdges(node2.id), ...store.outEdges(node2.id)]) {
84836
+ if (edge2.attrs["source"] === "cloud") store.mergeEdge({ ...edge2, attrs: { source: "cloud", historySourceWithdrawn: true } });
84837
+ }
84838
+ changed++;
84839
+ }
84840
+ }
84841
+ return changed;
84842
+ }
84843
+ async function syncHistoryCorrections(opts) {
84844
+ const { root, profile, store, cloud } = opts;
84845
+ if (!profile.projectId || !cloud.getProjectHistoryCorrections) return;
84846
+ let cursor = profile.projectHistoryCorrectionCursor ?? 0;
84847
+ for (let i2 = 0; i2 < 25; i2++) {
84848
+ const page = await cloud.getProjectHistoryCorrections(profile.projectId, cursor, 200);
84849
+ if (!Number.isSafeInteger(page.cursor) || page.cursor < cursor || !Array.isArray(page.changes) || page.changes.length > 200 || page.changes.some((c, j) => !Number.isSafeInteger(c.seq) || c.seq <= (page.changes[j - 1]?.seq ?? cursor) || c.seq > page.cursor || !["quarantine", "unreviewed"].includes(c.action) || c.sourceSupport !== void 0 && !["supported", "contradicted", "unreviewed", "quarantined"].includes(c.sourceSupport) || typeof c.nodeId !== "string" || typeof c.canonicalId !== "string")) {
84850
+ throw new Error("Invalid history correction page");
84851
+ }
84852
+ if (page.cursor !== (page.changes.at(-1)?.seq ?? cursor)) throw new Error("History correction cursor skipped a page");
84853
+ applyHistoryCorrections(store, page);
84854
+ for (const mirror of opts.mirrors ?? []) applyHistoryCorrections(mirror, page);
84855
+ if (page.changes.some((c) => c.sourceSupport === "supported")) {
84856
+ profile.projectAnchorCursor = 0;
84857
+ delete profile.historyRevisionCursor;
84858
+ }
84859
+ if (page.cursor > cursor) {
84860
+ cursor = page.cursor;
84861
+ profile.projectHistoryCorrectionCursor = cursor;
84862
+ saveProfile(root, profile);
84863
+ }
84864
+ if (page.changes.length < 200) break;
84865
+ }
84866
+ }
84867
+
84868
+ // src/project-reanchor.ts
84285
84869
  var DEFAULT_LIMIT = 50;
84286
84870
  var ANCHOR_SYMBOL_KINDS2 = [
84287
84871
  "Function",
@@ -84497,6 +85081,7 @@ async function syncProjectAnchors(opts) {
84497
85081
  let landed = 0;
84498
85082
  let cursor = profile.projectAnchorCursor ?? 0;
84499
85083
  try {
85084
+ await syncHistoryCorrections({ root, profile, store, cloud, ...opts.correctionMirrors ? { mirrors: opts.correctionMirrors } : {} });
84500
85085
  for (let page = 0; page < maxPages; page++) {
84501
85086
  const res = await cloud.getProjectAnchors(projectId, cursor, limit);
84502
85087
  if (res.anchors.length === 0) break;
@@ -84611,13 +85196,109 @@ async function hydrateProject(opts) {
84611
85196
  }
84612
85197
  }
84613
85198
 
85199
+ // src/history-reconcile.ts
85200
+ init_src11();
85201
+ init_src();
85202
+ import { execFile as execFile4 } from "node:child_process";
85203
+ import { promisify as promisify2 } from "node:util";
85204
+ import { extname as extname3 } from "node:path";
85205
+ init_profile2();
85206
+ var exec = promisify2(execFile4);
85207
+ var KINDS = { function: "Function", class: "Class", method: "Method", interface: "Interface", const: "Const", enum: "Enum", typealias: "TypeAlias", namespace: "Namespace", property: "Property" };
85208
+ async function syncHistoryRevision(opts) {
85209
+ const { root, profile, store, cloud, salt } = opts;
85210
+ if (!profile.projectId) return { changed: 0, remaining: false };
85211
+ const projectId = profile.projectId;
85212
+ const git3 = opts.git ?? (async (args2) => (await exec("git", ["-C", root, ...args2], { windowsHide: true, maxBuffer: 8 * 1024 * 1024 })).stdout);
85213
+ const atCommit = (await git3(["rev-parse", "--verify", "HEAD"])).trim();
85214
+ if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(atCommit)) throw new Error("history revision is not a full Git object id");
85215
+ const oldCursor = profile.historyRevisionCursor;
85216
+ const same = oldCursor?.atCommit === atCommit;
85217
+ if (same && oldCursor.complete && Date.now() - oldCursor.updatedAt < 60 * 6e4) return { changed: 0, remaining: false };
85218
+ const after = same && !oldCursor.complete ? oldCursor.after : void 0;
85219
+ const page = await cloud.getHistoryOccurrences(projectId, atCommit, after);
85220
+ 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");
85221
+ const parents = (await git3(["rev-list", "--parents", "--max-count=8192", atCommit])).trim().split("\n").filter(Boolean).map((row) => {
85222
+ const [commit, ...parents2] = row.trim().split(/\s+/);
85223
+ return { commit, parents: parents2 };
85224
+ });
85225
+ const reverse = buildReverseSymbolMap({ store, projectId, salt });
85226
+ const ignore2 = loadRepoIgnore(root, profile.ignoreExtra ?? []);
85227
+ const providers = defaultProviders();
85228
+ const parsedFiles = /* @__PURE__ */ new Map();
85229
+ const observations = [];
85230
+ for (const occurrence of page.occurrences) {
85231
+ const anchors = occurrence.quality["historicalAnchors"];
85232
+ const paths = /* @__PURE__ */ new Set(), symbols = [];
85233
+ for (const anchor of anchors ?? []) {
85234
+ const localId = reverse.get(anchor.symbolId), local = localId ? store.getNode(localId) : null;
85235
+ const path2 = local?.attrs["relPath"];
85236
+ if (typeof path2 !== "string" || !pathAllowedByFloor(path2) || isPathBlocked(ignore2, path2) || /[\r\n]/.test(path2)) continue;
85237
+ paths.add(path2);
85238
+ if (!parsedFiles.has(path2)) {
85239
+ const hashes = /* @__PURE__ */ new Map();
85240
+ parsedFiles.set(path2, hashes);
85241
+ const provider = providers.find((p) => p.fileExtensions.includes(extname3(path2).toLowerCase()));
85242
+ if (!provider) continue;
85243
+ const source2 = await git3(["show", `${atCommit}:${path2}`]).catch(() => null);
85244
+ if (source2 === null || Buffer.byteLength(source2) > 1e6 || source2.includes("\0")) continue;
85245
+ await provider.preload?.();
85246
+ for (const symbol2 of provider.extractSymbols(source2, path2)) {
85247
+ const kind = KINDS[symbol2.kind];
85248
+ if (kind && symbol2.bodyHash) hashes.set(projectSymbolId(salt, projectId, path2, symbol2.qname ?? symbol2.name, kind), symbol2.bodyHash);
85249
+ }
85250
+ }
85251
+ const bodyHash = parsedFiles.get(path2).get(anchor.symbolId);
85252
+ if (bodyHash) symbols.push({ symbolId: anchor.symbolId, bodyHash });
85253
+ }
85254
+ const source = occurrenceSourceCommit(occurrence);
85255
+ let diff;
85256
+ if (source && source !== atCommit && paths.size) {
85257
+ const bundle = await buildUnitBundle(root, {
85258
+ sha: atCommit,
85259
+ parents: [source],
85260
+ isMerge: true,
85261
+ index: 0,
85262
+ subIndex: 0,
85263
+ subject: "",
85264
+ body: "",
85265
+ authorDate: "",
85266
+ files: [...paths].map((path2) => ({ path: path2, adds: 1, dels: 1, binary: false }))
85267
+ }, { maxTokens: 4e3, ignorePatterns: profile.ignoreExtra ?? [] }).catch(() => null);
85268
+ if (bundle && !bundle.truncated) diff = bundle.diff;
85269
+ }
85270
+ observations.push({ occurrenceId: occurrence.occurrenceId, symbols, ...diff ? { diff } : {} });
85271
+ }
85272
+ const response = page.occurrences.length ? await cloud.reconcileHistory(projectId, { atCommit, parents, observations }) : { atCommit, receipts: [] };
85273
+ if (response.atCommit !== atCommit || response.receipts.length !== page.occurrences.length || new Set(response.receipts.map((r) => r.occurrenceId)).size !== response.receipts.length || response.receipts.some((r) => r.atCommit !== atCommit || !observations.some((o) => o.occurrenceId === r.occurrenceId))) throw new Error("incomplete history reconciliation response");
85274
+ if ((await git3(["rev-parse", "--verify", "HEAD"])).trim() !== atCommit) return { changed: 0, remaining: true };
85275
+ let changed = 0;
85276
+ for (const occurrence of page.occurrences) {
85277
+ const node2 = store.getNode(occurrence.canonicalId) ?? (occurrence.nodeId ? store.getNode(occurrence.nodeId) : null);
85278
+ if (!node2 || node2.attrs["source"] !== "cloud" || node2.attrs["historyQuality"]?.["sourceSupport"] === "quarantined") continue;
85279
+ const receipt = response.receipts.find((r) => r.occurrenceId === occurrence.occurrenceId);
85280
+ const previous = node2.attrs["historyQuality"];
85281
+ if (previous?.["evaluatedAtCommit"] === atCommit && previous["applicability"] === "applicable" && receipt.verdict !== "applicable") continue;
85282
+ store.mergeNode({ ...node2, attrs: { ...node2.attrs, historyQuality: {
85283
+ ...occurrence.quality,
85284
+ evaluatedAtCommit: atCommit,
85285
+ applicability: receipt.verdict,
85286
+ reconciliationEvidence: receipt.evidence
85287
+ } } });
85288
+ changed++;
85289
+ }
85290
+ profile.historyRevisionCursor = { atCommit, ...page.cursor ? { after: page.cursor } : {}, complete: page.cursor === null, updatedAt: Date.now() };
85291
+ (opts.save ?? ((p) => saveProfile(root, p)))(profile);
85292
+ return { changed, remaining: page.cursor !== null };
85293
+ }
85294
+
84614
85295
  // src/project-history.ts
84615
85296
  init_src7();
84616
85297
  init_src();
84617
- import { execFile as execFile4 } from "node:child_process";
85298
+ import { execFile as execFile5 } from "node:child_process";
84618
85299
  import { readFileSync as readFileSync32 } from "node:fs";
84619
85300
  import { join as join37 } from "node:path";
84620
- import { promisify as promisify2 } from "node:util";
85301
+ import { promisify as promisify3 } from "node:util";
84621
85302
 
84622
85303
  // src/history-walk.ts
84623
85304
  var LOCKFILE_NAMES = /* @__PURE__ */ new Set([
@@ -84935,10 +85616,13 @@ function message(u) {
84935
85616
  ${u.body}` : u.subject;
84936
85617
  }
84937
85618
  function narrative(unit, group) {
84938
- if (!group || group.length === 0) return message(unit).trim();
85619
+ return narrativeSources(unit, group).map((source) => source.text).join("\n\n");
85620
+ }
85621
+ function narrativeSources(unit, group) {
85622
+ if (!group || group.length === 0) return [{ commitSha: unit.sha, text: message(unit).trim() }].filter((s) => s.text.length > 0);
84939
85623
  const members = group.filter((g) => g.groupSha === (unit.groupSha ?? unit.sha)).sort((a, b) => a.subIndex - b.subIndex);
84940
85624
  const source = members.length > 0 ? members : [...group].sort((a, b) => a.subIndex - b.subIndex);
84941
- return source.map(message).map((m) => m.trim()).filter((m) => m.length > 0).join("\n\n");
85625
+ return source.map((member) => ({ commitSha: member.sha, text: message(member).trim() })).filter((s) => s.text.length > 0);
84942
85626
  }
84943
85627
 
84944
85628
  // src/git-batch.ts
@@ -85042,13 +85726,116 @@ var GitBlobReader = class {
85042
85726
  }
85043
85727
  };
85044
85728
 
85729
+ // src/history-anchors.ts
85730
+ init_src11();
85731
+ init_src();
85732
+ import { extname as extname4 } from "node:path";
85733
+ var LABELS = {
85734
+ function: "Function",
85735
+ class: "Class",
85736
+ method: "Method",
85737
+ interface: "Interface",
85738
+ const: "Const",
85739
+ enum: "Enum",
85740
+ typealias: "TypeAlias",
85741
+ namespace: "Namespace",
85742
+ property: "Property"
85743
+ };
85744
+ var MAX_SOURCE_BYTES = 1e6;
85745
+ var MAX_ANCHORS = 20;
85746
+ async function historicalAnchors(input) {
85747
+ if (!/^[a-f0-9]{40,64}$/.test(input.unit.sha)) return void 0;
85748
+ const ignore2 = loadRepoIgnore(input.root, input.ignorePatterns);
85749
+ const providers = input.providers ?? defaultProviders();
85750
+ const beforeTokens = /* @__PURE__ */ new Map();
85751
+ for (const line of input.bundle.diff.split("\n")) {
85752
+ const m = /^diff --git a\/(p\d+) b\/(p\d+)$/.exec(line);
85753
+ if (m) beforeTokens.set(m[2], m[1]);
85754
+ }
85755
+ const parsed = /* @__PURE__ */ new Map();
85756
+ const parse4 = (commit, token) => {
85757
+ const path2 = input.bundle.pathTokens[token];
85758
+ if (!path2 || /[\r\n]/.test(path2) || !pathAllowedByFloor(path2) || isPathBlocked(ignore2, path2)) return Promise.resolve(null);
85759
+ const provider = providers.find((p) => p.fileExtensions.includes(extname4(path2).toLowerCase()));
85760
+ if (!provider) return Promise.resolve(null);
85761
+ const spec = `${commit}:${path2}`;
85762
+ if (!parsed.has(spec)) parsed.set(spec, (async () => {
85763
+ try {
85764
+ const source = await input.readBlob(spec);
85765
+ if (source === null || source.includes("\0") || Buffer.byteLength(source) > MAX_SOURCE_BYTES) return null;
85766
+ await provider.preload?.();
85767
+ const symbols = provider.extractSymbols(source, path2).filter((s) => LABELS[s.kind] && s.startByte >= 0 && s.endByte > s.startByte);
85768
+ const offsets = [0];
85769
+ for (let i2 = 0; i2 < source.length; i2++) if (source[i2] === "\n") offsets.push(i2 + 1);
85770
+ offsets.push(source.length + 1);
85771
+ return { symbols, offsets };
85772
+ } catch {
85773
+ return null;
85774
+ }
85775
+ })());
85776
+ return parsed.get(spec);
85777
+ };
85778
+ const out2 = {};
85779
+ const blocks = historySourceBlocks({ narrative: "", diff: input.bundle.diff, commitSha: input.unit.sha });
85780
+ for (const block of blocks) {
85781
+ if (block.kind !== "diff" || !block.pathToken) continue;
85782
+ const lines = block.text.split("\n");
85783
+ const header = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(lines[0]);
85784
+ if (!header) continue;
85785
+ let before = Number(header[1]), after = Number(header[2]);
85786
+ let offset = Buffer.byteLength(lines[0]) + 1;
85787
+ const list = out2[block.pathToken] ?? [];
85788
+ for (const line of lines.slice(1)) {
85789
+ const end = offset + Buffer.byteLength(line);
85790
+ const side = line[0] === "+" ? "after" : line[0] === "-" ? "before" : null;
85791
+ if (side) {
85792
+ const commit = side === "after" ? input.unit.sha : input.unit.parents[0];
85793
+ const token = side === "after" ? block.pathToken : beforeTokens.get(block.pathToken);
85794
+ if (commit && /^[a-f0-9]{40,64}$/.test(commit) && token) {
85795
+ const file2 = await parse4(commit, token);
85796
+ const row = (side === "after" ? after : before) - 1;
85797
+ const start2 = file2?.offsets[row], stop2 = file2?.offsets[row + 1];
85798
+ if (file2 && start2 !== void 0 && stop2 !== void 0) {
85799
+ const symbols = file2.symbols.filter((s) => s.startByte < stop2 && s.endByte > start2).sort((a, b) => a.endByte - a.startByte - (b.endByte - b.startByte));
85800
+ const symbol2 = symbols[0];
85801
+ if (symbol2) {
85802
+ const kind = LABELS[symbol2.kind];
85803
+ const symbolId = projectSymbolId(input.salt, input.projectId, input.bundle.pathTokens[token], symbol2.qname ?? symbol2.name, kind);
85804
+ const prev = list.at(-1);
85805
+ if (prev?.symbolId === symbolId && prev.historical?.side === side && prev.historical.blockDigest === block.digest && prev.historical.endByte + 1 === offset) {
85806
+ prev.historical.endByte = end;
85807
+ } else if (list.length < MAX_ANCHORS) list.push({
85808
+ symbolId,
85809
+ kind,
85810
+ historical: {
85811
+ sourceCommitSha: input.unit.sha,
85812
+ commitSha: commit,
85813
+ side,
85814
+ blockDigest: block.digest,
85815
+ startByte: offset,
85816
+ endByte: end,
85817
+ ...symbol2.bodyHash ? { bodyHash: symbol2.bodyHash } : {}
85818
+ }
85819
+ });
85820
+ }
85821
+ }
85822
+ }
85823
+ }
85824
+ if (line[0] === " " || line[0] === "-") before++;
85825
+ if (line[0] === " " || line[0] === "+") after++;
85826
+ offset = end + 1;
85827
+ }
85828
+ if (list.length) out2[block.pathToken] = list;
85829
+ }
85830
+ return Object.keys(out2).length ? out2 : void 0;
85831
+ }
85832
+
85045
85833
  // src/project-history.ts
85046
85834
  init_profile2();
85047
- var execFileAsync2 = promisify2(execFile4);
85835
+ var execFileAsync2 = promisify3(execFile5);
85048
85836
  var HISTORY_EXTRACTOR_VERSION = "history-2";
85049
85837
  var LEASE_POLL_MS = 2e4;
85050
85838
  var RECOVERY_DEADLINE_MS = 15 * 6e4;
85051
- var MAX_ANCHORS_PER_TOKEN = 20;
85052
85839
  var DEFAULT_LOOKAHEAD = 2;
85053
85840
  async function defaultGit(root, args2) {
85054
85841
  const { stdout } = await execFileAsync2("git", ["-C", root, ...args2], { maxBuffer: 64 * 1024 * 1024, windowsHide: true });
@@ -85099,27 +85886,6 @@ async function spineForUnit(git3, root, unit, readBlob) {
85099
85886
  const wire = (d) => ({ ecosystem: d.ecosystem, name: d.name, version: d.version, kind: d.kind, manifest: d.manifest });
85100
85887
  return { declared: rings.declared.map(wire), context: rings.context.map(wire) };
85101
85888
  }
85102
- function anchorsFor(pathTokens, ignoreLines, projectId, anchors) {
85103
- if (!anchors) return void 0;
85104
- const out2 = {};
85105
- const ignored = (p) => ignoreLines.some((rule) => p === rule || p.startsWith(rule.replace(/\/+$/, "") + "/"));
85106
- for (const [token, relPath] of Object.entries(pathTokens)) {
85107
- if (ignored(relPath)) continue;
85108
- const nodes = anchors.index.nodesByRelPath([relPath]);
85109
- const list = [];
85110
- for (const n of nodes) {
85111
- const qname = n.attrs["qname"];
85112
- if (typeof qname !== "string") continue;
85113
- list.push({
85114
- symbolId: projectSymbolId(anchors.salt, projectId, relPath, qname, n.label),
85115
- kind: n.label
85116
- });
85117
- if (list.length >= MAX_ANCHORS_PER_TOKEN) break;
85118
- }
85119
- if (list.length > 0) out2[token] = list;
85120
- }
85121
- return Object.keys(out2).length > 0 ? out2 : void 0;
85122
- }
85123
85889
  function stage(profile, patch, now) {
85124
85890
  const at = now().toISOString();
85125
85891
  const prev = profile.historyStage;
@@ -85137,6 +85903,7 @@ function stage(profile, patch, now) {
85137
85903
  // keep showing the error it recovered from); an absent key keeps it.
85138
85904
  ..."lastError" in patch ? patch.lastError ? { lastError: patch.lastError } : {} : prev?.lastError ? { lastError: prev.lastError } : {},
85139
85905
  ...patch.counts ? { counts: patch.counts } : prev?.counts ? { counts: prev.counts } : {},
85906
+ ...patch.coverageGaps ? { coverageGaps: patch.coverageGaps } : prev?.coverageGaps ? { coverageGaps: prev.coverageGaps } : {},
85140
85907
  ...patch.backfillAnchor ? { backfillAnchor: patch.backfillAnchor } : prev?.backfillAnchor ? { backfillAnchor: prev.backfillAnchor } : {}
85141
85908
  }
85142
85909
  };
@@ -85156,7 +85923,7 @@ function unitTiming(postStarted, prepMs, receipt) {
85156
85923
  }
85157
85924
  function addCounts(a, b) {
85158
85925
  const sum = (k) => (a[k] ?? 0) + (b[k] ?? 0);
85159
- return { commitsWalked: sum("commitsWalked"), changesExtracted: sum("changesExtracted"), pairs: sum("pairs"), claims: sum("claims"), anchors: sum("anchors"), packages: sum("packages") };
85926
+ return { commitsWalked: sum("commitsWalked"), changesExtracted: sum("changesExtracted"), pairs: sum("pairs"), claims: sum("claims"), anchors: sum("anchors"), packages: sum("packages"), deferredUnits: sum("deferredUnits") };
85160
85927
  }
85161
85928
  async function runHistoryWalk(root, input, client, opts = {}) {
85162
85929
  const reader = opts.git ? null : new GitBlobReader(root);
@@ -85198,7 +85965,12 @@ async function walk10(root, input, client, opts, readBlob) {
85198
85965
  return { profile, processed: 0, state: "refused" };
85199
85966
  }
85200
85967
  if (units.length === 0) {
85201
- profile = stage(profile, { state: "done", watermarkSha: headSha }, now);
85968
+ const deferred = profile.historyStage?.counts?.deferredUnits ?? 0;
85969
+ profile = stage(profile, {
85970
+ state: "done",
85971
+ watermarkSha: headSha,
85972
+ ...deferred > 0 ? { lastError: `${deferred} history units deferred; coverage gaps require source review and re-extraction.` } : {}
85973
+ }, now);
85202
85974
  persist(profile);
85203
85975
  await reportHistoryStage(client, profile, log);
85204
85976
  return { profile, processed: 0, state: "done" };
@@ -85227,7 +85999,8 @@ async function walk10(root, input, client, opts, readBlob) {
85227
85999
  const group = byIndex.get(u.index) ?? [u];
85228
86000
  const bundle = await buildUnitBundle(root, u, {
85229
86001
  ignorePatterns: ignoreLines,
85230
- narrative: narrative(u, u.isMerge ? group : void 0)
86002
+ narrative: narrative(u, u.isMerge ? group : void 0),
86003
+ narrativeSources: narrativeSources(u, u.isMerge ? group : void 0)
85231
86004
  });
85232
86005
  const prev = dryRun ? void 0 : predecessor(k);
85233
86006
  const body2 = {
@@ -85240,6 +86013,7 @@ async function walk10(root, input, client, opts, readBlob) {
85240
86013
  ...u.authorDate ? { authorDate: u.authorDate } : {}
85241
86014
  },
85242
86015
  narrative: bundle.narrative,
86016
+ ...bundle.narrativeSources?.length ? { narrativeSources: bundle.narrativeSources } : {},
85243
86017
  fileList: bundle.fileList,
85244
86018
  diff: bundle.diff,
85245
86019
  pathTokens: Object.keys(bundle.pathTokens),
@@ -85251,7 +86025,15 @@ async function walk10(root, input, client, opts, readBlob) {
85251
86025
  };
85252
86026
  const spine = await spineForUnit(git3, root, u, readBlob);
85253
86027
  if (spine) body2.spine = spine;
85254
- const anchors = anchorsFor(bundle.pathTokens, ignoreLines, projectId, opts.anchors);
86028
+ const anchors = opts.anchors ? await historicalAnchors({
86029
+ root,
86030
+ projectId,
86031
+ salt: opts.anchors.salt,
86032
+ unit: u,
86033
+ bundle,
86034
+ ignorePatterns: ignoreLines,
86035
+ readBlob
86036
+ }) : void 0;
85255
86037
  if (anchors) body2.anchors = anchors;
85256
86038
  return { body: body2, prepMs: Date.now() - prepStarted };
85257
86039
  })();
@@ -85384,6 +86166,15 @@ async function walk10(root, input, client, opts, readBlob) {
85384
86166
  return { profile, processed, state: "refused" };
85385
86167
  case "rejected":
85386
86168
  case "error":
86169
+ if (outcome.status === "error" && outcome.parked && outcome.runId && ["deferred-context", "deferred-budget"].includes(outcome.errorCode ?? "")) {
86170
+ const gaps = [...profile.historyStage?.coverageGaps ?? []];
86171
+ const known = gaps.some((g) => g.runId === outcome.runId);
86172
+ counts = addCounts(counts, { commitsWalked: 1, deferredUnits: known ? 0 : 1 });
86173
+ if (!known) gaps.push({ sha: u.sha, subIndex: u.subIndex, runId: outcome.runId, reason: outcome.errorCode });
86174
+ profile = stage(profile, { coverageGaps: gaps.slice(-1e3) }, now);
86175
+ log(`[errata] history ${label}: deferred (${outcome.errorCode}); coverage gap retained as ${outcome.runId}`);
86176
+ break;
86177
+ }
85387
86178
  profile = stage(profile, { state: "paused", lastError: `unit ${label} ${outcome.status}: ${outcome.error.slice(0, 200)}` }, now);
85388
86179
  persist(profile);
85389
86180
  await reportHistoryStage(client, profile, log);
@@ -85394,7 +86185,11 @@ async function walk10(root, input, client, opts, readBlob) {
85394
86185
  processed += 1;
85395
86186
  }
85396
86187
  const reachedHead = (profile.historyStage?.unitsDone ?? 0) >= total;
85397
- profile = stage(profile, reachedHead ? { state: "done", watermarkSha: headSha } : { state: dryRun ? "extracting" : "contributing" }, now);
86188
+ profile = stage(profile, reachedHead ? {
86189
+ state: "done",
86190
+ watermarkSha: headSha,
86191
+ ...(counts.deferredUnits ?? 0) > 0 ? { lastError: `${counts.deferredUnits} history units deferred; coverage gaps require source review and re-extraction.` } : {}
86192
+ } : { state: dryRun ? "extracting" : "contributing" }, now);
85398
86193
  persist(profile);
85399
86194
  await reportHistoryStage(client, profile, log);
85400
86195
  return { profile, processed, state: profile.historyStage?.state ?? "done" };
@@ -85477,7 +86272,7 @@ async function startHistoryStage(root, input, client, walk11 = {}) {
85477
86272
 
85478
86273
  // src/link-flow.ts
85479
86274
  init_cloud_auth();
85480
- import { createHash as createHash17 } from "node:crypto";
86275
+ import { createHash as createHash19 } from "node:crypto";
85481
86276
  import { execFileSync as execFileSync5 } from "node:child_process";
85482
86277
  import { existsSync as existsSync30, writeFileSync as writeFileSync23 } from "node:fs";
85483
86278
  import { join as join38 } from "node:path";
@@ -85771,7 +86566,7 @@ async function loginOAuthLoopback(opts) {
85771
86566
 
85772
86567
  // src/link-flow.ts
85773
86568
  function remoteFingerprint(locator) {
85774
- return createHash17("sha256").update(locator).digest("hex");
86569
+ return createHash19("sha256").update(locator).digest("hex");
85775
86570
  }
85776
86571
  function isDecision(value) {
85777
86572
  if (!value || typeof value !== "object") return false;
@@ -87057,13 +87852,24 @@ async function startMultiDaemon(opts = {}) {
87057
87852
  });
87058
87853
  const pullProjectAnchors = async (r, client, salt) => {
87059
87854
  const donePass = markPass(`anchor-pull:${r.engine.profile.name}`);
87855
+ const beforeCorrections = r.engine.profile.projectHistoryCorrectionCursor ?? 0;
87060
87856
  const sync = await syncProjectAnchors({
87061
87857
  root: r.root,
87062
87858
  profile: r.engine.profile,
87063
87859
  store: r.engine.store,
87860
+ correctionMirrors: [sharedStore],
87064
87861
  cloud: client,
87065
87862
  salt
87066
87863
  }).finally(donePass);
87864
+ if ((r.engine.profile.projectHistoryCorrectionCursor ?? 0) > beforeCorrections) {
87865
+ for (const record2 of records) record2.engine.markContextDirty();
87866
+ }
87867
+ try {
87868
+ const revision = await syncHistoryRevision({ root: r.root, profile: r.engine.profile, store: r.engine.store, cloud: client, salt });
87869
+ if (revision.changed) r.engine.markContextDirty();
87870
+ } catch (err2) {
87871
+ console.warn(`[errata] history revision reconciliation failed for ${r.engine.profile.name}: ${err2 instanceof Error ? err2.message : String(err2)}`);
87872
+ }
87067
87873
  if (sync.synced) {
87068
87874
  if (sync.anchors + sync.updated > 0) {
87069
87875
  r.engine.markContextDirty();