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

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;
@@ -32901,7 +33510,7 @@ var init_src10 = __esm({
32901
33510
  });
32902
33511
 
32903
33512
  // ../../packages/indexer/src/parse-cache.ts
32904
- import { createHash as createHash4 } from "node:crypto";
33513
+ import { createHash as createHash5 } from "node:crypto";
32905
33514
  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
33515
  import { homedir as homedir3 } from "node:os";
32907
33516
  import { join as join11 } from "node:path";
@@ -32909,7 +33518,7 @@ function parseCacheDir() {
32909
33518
  return process.env["ERRATA_PARSE_CACHE"] ?? join11(homedir3(), ".errata", "parse-cache");
32910
33519
  }
32911
33520
  function parseCacheKey(source, providerId) {
32912
- return createHash4("sha256").update(`${PARSE_CACHE_VERSION}:${providerId}:${source}`).digest("hex");
33521
+ return createHash5("sha256").update(`${PARSE_CACHE_VERSION}:${providerId}:${source}`).digest("hex");
32913
33522
  }
32914
33523
  function entryPath(dir, key) {
32915
33524
  return join11(dir, key.slice(0, 2), `${key.slice(2)}.json`);
@@ -33207,7 +33816,7 @@ var init_identity3 = __esm({
33207
33816
  import { appendFileSync, readFileSync as readFileSync11, statSync as statSync3 } from "node:fs";
33208
33817
  import { readdir } from "node:fs/promises";
33209
33818
  import { extname, join as join12, relative as relative2, sep } from "node:path";
33210
- import { createHash as createHash5 } from "node:crypto";
33819
+ import { createHash as createHash6 } from "node:crypto";
33211
33820
  import { execFileSync } from "node:child_process";
33212
33821
  function nowTs() {
33213
33822
  return indexNow ?? Date.now();
@@ -33216,7 +33825,7 @@ function fingerprintOf(signature, bodyHash) {
33216
33825
  return `${signature ?? ""}|${bodyHash ?? ""}`;
33217
33826
  }
33218
33827
  function sha(s) {
33219
- return createHash5("sha256").update(s).digest("hex").slice(0, 16);
33828
+ return createHash6("sha256").update(s).digest("hex").slice(0, 16);
33220
33829
  }
33221
33830
  function fileNodeId(workspaceId2, relPath) {
33222
33831
  return `file_${sha(workspaceId2 + ":" + relPath.replace(/\\/g, "/"))}`;
@@ -33344,7 +33953,7 @@ async function incrementalReindex(store, rootPath, workspaceId2, changedAbsPaths
33344
33953
  for (const rel of [...changedRelPaths]) {
33345
33954
  let h;
33346
33955
  try {
33347
- h = createHash5("sha256").update(readFileSync11(join12(rootPath, rel))).digest("hex");
33956
+ h = createHash6("sha256").update(readFileSync11(join12(rootPath, rel))).digest("hex");
33348
33957
  } catch {
33349
33958
  continue;
33350
33959
  }
@@ -34267,7 +34876,7 @@ function upsertFile(store, id, f, workspaceId2, skip) {
34267
34876
  if (existing && fileUnchanged(existing.attrs, f, skip)) return;
34268
34877
  let contentHash;
34269
34878
  try {
34270
- contentHash = createHash5("sha256").update(readFileSync11(f.absPath)).digest("hex");
34879
+ contentHash = createHash6("sha256").update(readFileSync11(f.absPath)).digest("hex");
34271
34880
  } catch {
34272
34881
  }
34273
34882
  const carried = { ...existing?.attrs ?? {} };
@@ -38796,7 +39405,7 @@ var init_tree_sitter_loader = __esm({
38796
39405
  });
38797
39406
 
38798
39407
  // ../../packages/indexer/src/languages/typescript-treesitter.ts
38799
- import { createHash as createHash6 } from "node:crypto";
39408
+ import { createHash as createHash7 } from "node:crypto";
38800
39409
  function extractNode(node2, source, containerQname, containerKind) {
38801
39410
  switch (node2.type) {
38802
39411
  case "function_declaration":
@@ -39009,7 +39618,7 @@ function scopeRecord(kind, name2, qname, node2, body2, source) {
39009
39618
  const sig = header.replace(/\s+/g, " ").trim();
39010
39619
  if (sig) rec.signature = sig;
39011
39620
  const bodyText = source.slice(body2.startIndex, body2.endIndex);
39012
- rec.bodyHash = createHash6("sha256").update(bodyText).digest("hex").slice(0, 16);
39621
+ rec.bodyHash = createHash7("sha256").update(bodyText).digest("hex").slice(0, 16);
39013
39622
  rec.bodySimhash = toHex(simhash(bodyText));
39014
39623
  }
39015
39624
  }
@@ -39451,14 +40060,14 @@ var init_typescript_treesitter = __esm({
39451
40060
  });
39452
40061
 
39453
40062
  // ../../packages/indexer/src/languages/python-treesitter.ts
39454
- import { createHash as createHash7 } from "node:crypto";
40063
+ import { createHash as createHash8 } from "node:crypto";
39455
40064
  function sigAndHash(node2, body2, source) {
39456
40065
  if (!body2) return {};
39457
40066
  const out2 = {};
39458
40067
  const sig = source.slice(node2.startIndex, body2.startIndex).replace(/\s+/g, " ").trim();
39459
40068
  if (sig) out2.signature = sig;
39460
40069
  const bodyText = source.slice(body2.startIndex, body2.endIndex);
39461
- out2.bodyHash = createHash7("sha256").update(bodyText).digest("hex").slice(0, 16);
40070
+ out2.bodyHash = createHash8("sha256").update(bodyText).digest("hex").slice(0, 16);
39462
40071
  out2.bodySimhash = toHex(simhash(bodyText));
39463
40072
  return out2;
39464
40073
  }
@@ -39871,7 +40480,7 @@ var init_python_treesitter = __esm({
39871
40480
  });
39872
40481
 
39873
40482
  // ../../packages/indexer/src/languages/cpp-treesitter.ts
39874
- import { createHash as createHash8 } from "node:crypto";
40483
+ import { createHash as createHash9 } from "node:crypto";
39875
40484
  function extractNode3(node2, containerQname, containerIsClass, source) {
39876
40485
  switch (node2.type) {
39877
40486
  case "function_definition":
@@ -40055,7 +40664,7 @@ function sigAndHash2(node2, body2, source) {
40055
40664
  const sig = source.slice(node2.startIndex, body2.startIndex).replace(/\s+/g, " ").trim();
40056
40665
  if (sig) out2.signature = sig;
40057
40666
  const bodyText = source.slice(body2.startIndex, body2.endIndex);
40058
- out2.bodyHash = createHash8("sha256").update(bodyText).digest("hex").slice(0, 16);
40667
+ out2.bodyHash = createHash9("sha256").update(bodyText).digest("hex").slice(0, 16);
40059
40668
  out2.bodySimhash = toHex(simhash(bodyText));
40060
40669
  return out2;
40061
40670
  }
@@ -40216,7 +40825,7 @@ var init_cpp_treesitter = __esm({
40216
40825
  });
40217
40826
 
40218
40827
  // ../../packages/indexer/src/languages/go-treesitter.ts
40219
- import { createHash as createHash9 } from "node:crypto";
40828
+ import { createHash as createHash10 } from "node:crypto";
40220
40829
  function extractNode4(node2, containerQname, source) {
40221
40830
  switch (node2.type) {
40222
40831
  case "function_declaration": {
@@ -40378,7 +40987,7 @@ function sigAndHash3(node2, body2, source) {
40378
40987
  const sig = source.slice(node2.startIndex, body2.startIndex).replace(/\s+/g, " ").trim();
40379
40988
  if (sig) out2.signature = sig;
40380
40989
  const bodyText = source.slice(body2.startIndex, body2.endIndex);
40381
- out2.bodyHash = createHash9("sha256").update(bodyText).digest("hex").slice(0, 16);
40990
+ out2.bodyHash = createHash10("sha256").update(bodyText).digest("hex").slice(0, 16);
40382
40991
  out2.bodySimhash = toHex(simhash(bodyText));
40383
40992
  return out2;
40384
40993
  }
@@ -40522,7 +41131,7 @@ var init_go_treesitter = __esm({
40522
41131
  });
40523
41132
 
40524
41133
  // ../../packages/indexer/src/languages/rust-treesitter.ts
40525
- import { createHash as createHash10 } from "node:crypto";
41134
+ import { createHash as createHash11 } from "node:crypto";
40526
41135
  function extractNode5(node2, containerQname, containerIsClass, source) {
40527
41136
  switch (node2.type) {
40528
41137
  case "function_item":
@@ -40737,7 +41346,7 @@ function sigAndHash4(node2, body2, source) {
40737
41346
  const sig = source.slice(node2.startIndex, body2.startIndex).replace(/\s+/g, " ").trim();
40738
41347
  if (sig) out2.signature = sig;
40739
41348
  const bodyText = source.slice(body2.startIndex, body2.endIndex);
40740
- out2.bodyHash = createHash10("sha256").update(bodyText).digest("hex").slice(0, 16);
41349
+ out2.bodyHash = createHash11("sha256").update(bodyText).digest("hex").slice(0, 16);
40741
41350
  out2.bodySimhash = toHex(simhash(bodyText));
40742
41351
  return out2;
40743
41352
  }
@@ -40888,7 +41497,7 @@ var init_rust_treesitter = __esm({
40888
41497
  });
40889
41498
 
40890
41499
  // ../../packages/indexer/src/languages/ruby-treesitter.ts
40891
- import { createHash as createHash11 } from "node:crypto";
41500
+ import { createHash as createHash12 } from "node:crypto";
40892
41501
  function sigAndHash5(node2, body2, source) {
40893
41502
  if (!body2) {
40894
41503
  const sig2 = source.slice(node2.startIndex, node2.endIndex).replace(/\s+/g, " ").trim();
@@ -40898,7 +41507,7 @@ function sigAndHash5(node2, body2, source) {
40898
41507
  const sig = source.slice(node2.startIndex, body2.startIndex).replace(/\s+/g, " ").trim();
40899
41508
  if (sig) out2.signature = sig;
40900
41509
  const bodyText = source.slice(body2.startIndex, body2.endIndex);
40901
- out2.bodyHash = createHash11("sha256").update(bodyText).digest("hex").slice(0, 16);
41510
+ out2.bodyHash = createHash12("sha256").update(bodyText).digest("hex").slice(0, 16);
40902
41511
  out2.bodySimhash = toHex(simhash(bodyText));
40903
41512
  return out2;
40904
41513
  }
@@ -41244,7 +41853,7 @@ var init_ruby_treesitter = __esm({
41244
41853
  });
41245
41854
 
41246
41855
  // ../../packages/indexer/src/languages/csharp-treesitter.ts
41247
- import { createHash as createHash12 } from "node:crypto";
41856
+ import { createHash as createHash13 } from "node:crypto";
41248
41857
  function extractNode7(node2, containerQname, containerIsType, source, eof) {
41249
41858
  switch (node2.type) {
41250
41859
  case "method_declaration":
@@ -41446,7 +42055,7 @@ function sigAndHash6(node2, body2, source) {
41446
42055
  const sig = source.slice(node2.startIndex, body2.startIndex).replace(/\s+/g, " ").trim();
41447
42056
  if (sig) out2.signature = sig;
41448
42057
  const bodyText = source.slice(body2.startIndex, body2.endIndex);
41449
- out2.bodyHash = createHash12("sha256").update(bodyText).digest("hex").slice(0, 16);
42058
+ out2.bodyHash = createHash13("sha256").update(bodyText).digest("hex").slice(0, 16);
41450
42059
  out2.bodySimhash = toHex(simhash(bodyText));
41451
42060
  return out2;
41452
42061
  }
@@ -51362,10 +51971,10 @@ import {
51362
51971
  writeFileSync as writeFileSync11
51363
51972
  } from "node:fs";
51364
51973
  import { join as join16 } from "node:path";
51365
- import { createHash as createHash13 } from "node:crypto";
51974
+ import { createHash as createHash14 } from "node:crypto";
51366
51975
  function enqueueOutbox(paths, payload) {
51367
51976
  ensureDir(paths.outbox);
51368
- const id = createHash13("sha256").update(JSON.stringify(payload)).digest("hex").slice(0, 16);
51977
+ const id = createHash14("sha256").update(JSON.stringify(payload)).digest("hex").slice(0, 16);
51369
51978
  const file2 = join16(paths.outbox, `${Date.now()}-${id}.json`);
51370
51979
  writeFileSync11(file2, JSON.stringify(payload, null, 2), "utf8");
51371
51980
  return file2;
@@ -63247,7 +63856,7 @@ var require_mock_interceptor = __commonJS({
63247
63856
  var require_mock_client = __commonJS({
63248
63857
  "../../node_modules/.pnpm/undici@6.24.1/node_modules/undici/lib/mock/mock-client.js"(exports, module2) {
63249
63858
  "use strict";
63250
- var { promisify: promisify3 } = __require("node:util");
63859
+ var { promisify: promisify4 } = __require("node:util");
63251
63860
  var Client = require_client();
63252
63861
  var { buildMockDispatch } = require_mock_utils();
63253
63862
  var {
@@ -63287,7 +63896,7 @@ var require_mock_client = __commonJS({
63287
63896
  return new MockInterceptor(opts, this[kDispatches]);
63288
63897
  }
63289
63898
  async [kClose]() {
63290
- await promisify3(this[kOriginalClose])();
63899
+ await promisify4(this[kOriginalClose])();
63291
63900
  this[kConnected] = 0;
63292
63901
  this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
63293
63902
  }
@@ -63300,7 +63909,7 @@ var require_mock_client = __commonJS({
63300
63909
  var require_mock_pool = __commonJS({
63301
63910
  "../../node_modules/.pnpm/undici@6.24.1/node_modules/undici/lib/mock/mock-pool.js"(exports, module2) {
63302
63911
  "use strict";
63303
- var { promisify: promisify3 } = __require("node:util");
63912
+ var { promisify: promisify4 } = __require("node:util");
63304
63913
  var Pool = require_pool();
63305
63914
  var { buildMockDispatch } = require_mock_utils();
63306
63915
  var {
@@ -63340,7 +63949,7 @@ var require_mock_pool = __commonJS({
63340
63949
  return new MockInterceptor(opts, this[kDispatches]);
63341
63950
  }
63342
63951
  async [kClose]() {
63343
- await promisify3(this[kOriginalClose])();
63952
+ await promisify4(this[kOriginalClose])();
63344
63953
  this[kConnected] = 0;
63345
63954
  this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
63346
63955
  }
@@ -74104,7 +74713,7 @@ init_checkout_root();
74104
74713
  import { closeSync, existsSync as existsSync16, fstatSync, openSync, readdirSync as readdirSync6, readSync, realpathSync, statSync as statSync6 } from "node:fs";
74105
74714
  import { basename as basename5, delimiter, dirname as dirname10, join as join20, normalize as normalize3 } from "node:path";
74106
74715
  import { homedir as homedir4 } from "node:os";
74107
- import { createHash as createHash14 } from "node:crypto";
74716
+ import { createHash as createHash15 } from "node:crypto";
74108
74717
  function readFrom(path2, fromByte, maxBytes) {
74109
74718
  let fd;
74110
74719
  try {
@@ -74413,7 +75022,7 @@ function parseCodexAssistantTurns(raw2, skipFirstLineParseFailure = false, start
74413
75022
  turns.push({
74414
75023
  // Slice-local line numbers repeat on every Stop. Position distinguishes
74415
75024
  // 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")}`,
75025
+ uuid: typeof payload["id"] === "string" && payload["id"].length > 0 ? payload["id"] : `codex-${lineByte}-${createHash15("sha256").update(line.trimEnd()).digest("hex")}`,
74417
75026
  text: parts2.join("\n\n"),
74418
75027
  hostHarness: "codex"
74419
75028
  });
@@ -74662,7 +75271,7 @@ function geminiSessionRoots(env2 = process.env, home = homedir4()) {
74662
75271
  return [...roots];
74663
75272
  }
74664
75273
  function geminiProjectHash(cwd) {
74665
- return createHash14("sha256").update(cwd).digest("hex");
75274
+ return createHash15("sha256").update(cwd).digest("hex");
74666
75275
  }
74667
75276
  function geminiHashesFor(cwd) {
74668
75277
  const variants = /* @__PURE__ */ new Set([cwd, cwd.replace(/\/+$/, "")]);
@@ -77983,7 +78592,7 @@ var TelemetryRecorder = class {
77983
78592
  // src/file-activity.ts
77984
78593
  import { readFileSync as readFileSync22 } from "node:fs";
77985
78594
  import { join as join28 } from "node:path";
77986
- import { createHash as createHash15 } from "node:crypto";
78595
+ import { createHash as createHash16 } from "node:crypto";
77987
78596
 
77988
78597
  // ../../packages/privacy/src/patterns/credentials.ts
77989
78598
  var CREDENTIAL_PATTERNS = [
@@ -78732,6 +79341,7 @@ var openaiAgent = new import_undici.Agent({
78732
79341
  init_git_remote();
78733
79342
 
78734
79343
  // src/history-bundle.ts
79344
+ init_src();
78735
79345
  import { execFile as execFile2 } from "node:child_process";
78736
79346
  import { readFileSync as readFileSync21 } from "node:fs";
78737
79347
  import { join as join27 } from "node:path";
@@ -78740,8 +79350,6 @@ var FILE_LIST_CAP = 60;
78740
79350
  var DIFF_FILE_CAP = 40;
78741
79351
  var GENERATED_JSON_MAX_CHURN = 800;
78742
79352
  var TRUNCATION_MARKER = "\n[truncated]";
78743
- var REDACTED_CREDENTIAL = "[redacted:credential]";
78744
- var REDACTED_EMAIL = "[redacted:email]";
78745
79353
  var CHARS_PER_TOKEN = 3.5;
78746
79354
  function estimateTokens(text) {
78747
79355
  return Math.ceil(text.length / CHARS_PER_TOKEN);
@@ -78882,138 +79490,6 @@ function isSourceFile(f) {
78882
79490
  if (ext === "json" && f.adds + f.dels > GENERATED_JSON_MAX_CHURN) return false;
78883
79491
  return true;
78884
79492
  }
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
79493
  var TRAILER_KEYS = [
79018
79494
  "signed-off-by",
79019
79495
  "co-authored-by",
@@ -79135,12 +79611,18 @@ async function buildUnitBundle(root, unit, opts = {}) {
79135
79611
  const rawDiff = await unitDiff(root, unit, diffPaths);
79136
79612
  for (const p of headerPaths(rawDiff)) tokens.token(p);
79137
79613
  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;
79614
+ const n = scrubHistoryText(tokens.apply(stripTrailers(narrativeRaw)));
79615
+ const fl = scrubHistoryText(tokens.apply(fileListRaw));
79616
+ const d = scrubHistoryText(tokens.apply(rawDiff));
79617
+ const safe = scrubHistoryValue({
79618
+ narrative: tokens.apply(stripTrailers(narrativeRaw)),
79619
+ fileList: tokens.apply(fileListRaw),
79620
+ diff: tokens.apply(rawDiff),
79621
+ narrativeSources: opts.narrativeSources?.map((source) => ({ commitSha: source.commitSha, text: tokens.apply(stripTrailers(source.text)).trim() }))
79622
+ });
79623
+ let narrative2 = safe.narrative;
79624
+ let fileList = safe.fileList;
79625
+ let diff = safe.diff;
79144
79626
  const separators = 2;
79145
79627
  let prose = estimateTokens(narrative2) + estimateTokens(fileList) + separators;
79146
79628
  if (prose > maxTokens) {
@@ -79159,6 +79641,7 @@ async function buildUnitBundle(root, unit, opts = {}) {
79159
79641
  return {
79160
79642
  sha: unit.sha,
79161
79643
  narrative: narrative2,
79644
+ ...safe.narrativeSources ? { narrativeSources: safe.narrativeSources.filter((source) => source.text.length > 0 && narrative2.includes(source.text)).slice(0, 64) } : {},
79162
79645
  fileList,
79163
79646
  diff,
79164
79647
  tokenEstimate: estimateTokens(narrative2) + estimateTokens(fileList) + estimateTokens(diff) + separators,
@@ -79281,7 +79764,7 @@ var FileActivityReporter = class {
79281
79764
  const current = this.options.binding();
79282
79765
  if (!identity || this.stopped || !current.enabled || current.projectId !== binding.projectId)
79283
79766
  return false;
79284
- const hash2 = createHash15("sha256").update(JSON.stringify([identity, source, at, clean, operation, branch, previousBranch])).digest("hex");
79767
+ const hash2 = createHash16("sha256").update(JSON.stringify([identity, source, at, clean, operation, branch, previousBranch])).digest("hex");
79285
79768
  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
79769
  if (!this.pending.some((item) => item.event.id === id))
79287
79770
  this.pending.push({
@@ -80338,7 +80821,7 @@ function createWatchBreaker(deps) {
80338
80821
  }
80339
80822
 
80340
80823
  // src/engine.ts
80341
- var DAEMON_VERSION = true ? "2.0.13-dev.2998" : "2.0.0-alpha.0";
80824
+ var DAEMON_VERSION = true ? "2.0.13-dev.3043" : "2.0.0-alpha.0";
80342
80825
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
80343
80826
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
80344
80827
  var GIT_OP_MUTE_MS = 4e3;
@@ -82797,10 +83280,10 @@ init_registry();
82797
83280
  init_identity2();
82798
83281
 
82799
83282
  // src/instance-receipts.ts
82800
- import { createHash as createHash16 } from "node:crypto";
83283
+ import { createHash as createHash17 } from "node:crypto";
82801
83284
  var FIELDS = ["contributedAtSeq", "cloudNodeId", "anchorsContributedDigest", "projectAnchorsContributedDigest", "semanticEdgesContributedDigest", "shipRefusedSinceSeq", "shipRefusedCount", "shipRefusedReason", "shipRefusedFingerprint"];
82802
83285
  function instanceDestinationKey(input) {
82803
- return createHash16("sha256").update(JSON.stringify([
83286
+ return createHash17("sha256").update(JSON.stringify([
82804
83287
  input.workspace,
82805
83288
  input.cloudUrl.replace(/\/+$/, ""),
82806
83289
  input.installationId ?? input.profileName ?? "default",
@@ -84282,6 +84765,69 @@ init_profile2();
84282
84765
  init_src();
84283
84766
  init_src11();
84284
84767
  init_profile2();
84768
+
84769
+ // src/history-corrections.ts
84770
+ init_profile2();
84771
+ function applyHistoryCorrections(store, page) {
84772
+ let changed = 0;
84773
+ for (const correction of page.changes) {
84774
+ const key = `history-correction:${correction.nodeId}`;
84775
+ const previous = store.getMeta?.(key);
84776
+ if (!previous || Number(JSON.parse(previous).seq) < correction.seq) store.setMeta?.(key, JSON.stringify(correction));
84777
+ const current = store.getNode(correction.canonicalId) ?? store.getNode(correction.nodeId);
84778
+ if (!current) continue;
84779
+ const privacy = correction.action === "quarantine";
84780
+ const copies = privacy ? store.nodeHistory(current.logicalId ?? current.id) : [current];
84781
+ for (const node2 of copies) {
84782
+ if (node2.attrs["source"] !== "cloud" || node2.attrs["cloudNodeId"] && node2.attrs["cloudNodeId"] !== correction.nodeId || Number(node2.attrs["historyCorrectionSeq"] ?? 0) >= correction.seq) continue;
84783
+ const quality = { sourceSupport: correction.sourceSupport ?? (privacy ? "quarantined" : "unreviewed"), applicability: "unknown" };
84784
+ store.mergeNode({
84785
+ ...node2,
84786
+ lastUpdatedAt: Date.now(),
84787
+ ...privacy ? { description: "History source withdrawn.", embedding: [], isLandmark: false } : {},
84788
+ attrs: {
84789
+ ...privacy ? {} : node2.attrs,
84790
+ source: "cloud",
84791
+ cloudNodeId: correction.nodeId,
84792
+ extractionSource: "history-mined",
84793
+ historyQuality: quality,
84794
+ historyCorrectionSeq: correction.seq
84795
+ }
84796
+ });
84797
+ if (privacy) for (const edge2 of [...store.inEdges(node2.id), ...store.outEdges(node2.id)]) {
84798
+ if (edge2.attrs["source"] === "cloud") store.mergeEdge({ ...edge2, attrs: { source: "cloud", historySourceWithdrawn: true } });
84799
+ }
84800
+ changed++;
84801
+ }
84802
+ }
84803
+ return changed;
84804
+ }
84805
+ async function syncHistoryCorrections(opts) {
84806
+ const { root, profile, store, cloud } = opts;
84807
+ if (!profile.projectId || !cloud.getProjectHistoryCorrections) return;
84808
+ let cursor = profile.projectHistoryCorrectionCursor ?? 0;
84809
+ for (let i2 = 0; i2 < 25; i2++) {
84810
+ const page = await cloud.getProjectHistoryCorrections(profile.projectId, cursor, 200);
84811
+ 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")) {
84812
+ throw new Error("Invalid history correction page");
84813
+ }
84814
+ if (page.cursor !== (page.changes.at(-1)?.seq ?? cursor)) throw new Error("History correction cursor skipped a page");
84815
+ applyHistoryCorrections(store, page);
84816
+ for (const mirror of opts.mirrors ?? []) applyHistoryCorrections(mirror, page);
84817
+ if (page.changes.some((c) => c.sourceSupport === "supported")) {
84818
+ profile.projectAnchorCursor = 0;
84819
+ delete profile.historyRevisionCursor;
84820
+ }
84821
+ if (page.cursor > cursor) {
84822
+ cursor = page.cursor;
84823
+ profile.projectHistoryCorrectionCursor = cursor;
84824
+ saveProfile(root, profile);
84825
+ }
84826
+ if (page.changes.length < 200) break;
84827
+ }
84828
+ }
84829
+
84830
+ // src/project-reanchor.ts
84285
84831
  var DEFAULT_LIMIT = 50;
84286
84832
  var ANCHOR_SYMBOL_KINDS2 = [
84287
84833
  "Function",
@@ -84497,6 +85043,7 @@ async function syncProjectAnchors(opts) {
84497
85043
  let landed = 0;
84498
85044
  let cursor = profile.projectAnchorCursor ?? 0;
84499
85045
  try {
85046
+ await syncHistoryCorrections({ root, profile, store, cloud, ...opts.correctionMirrors ? { mirrors: opts.correctionMirrors } : {} });
84500
85047
  for (let page = 0; page < maxPages; page++) {
84501
85048
  const res = await cloud.getProjectAnchors(projectId, cursor, limit);
84502
85049
  if (res.anchors.length === 0) break;
@@ -84611,13 +85158,109 @@ async function hydrateProject(opts) {
84611
85158
  }
84612
85159
  }
84613
85160
 
85161
+ // src/history-reconcile.ts
85162
+ init_src11();
85163
+ init_src();
85164
+ import { execFile as execFile4 } from "node:child_process";
85165
+ import { promisify as promisify2 } from "node:util";
85166
+ import { extname as extname3 } from "node:path";
85167
+ init_profile2();
85168
+ var exec = promisify2(execFile4);
85169
+ var KINDS = { function: "Function", class: "Class", method: "Method", interface: "Interface", const: "Const", enum: "Enum", typealias: "TypeAlias", namespace: "Namespace", property: "Property" };
85170
+ async function syncHistoryRevision(opts) {
85171
+ const { root, profile, store, cloud, salt } = opts;
85172
+ if (!profile.projectId) return { changed: 0, remaining: false };
85173
+ const projectId = profile.projectId;
85174
+ const git3 = opts.git ?? (async (args2) => (await exec("git", ["-C", root, ...args2], { windowsHide: true, maxBuffer: 8 * 1024 * 1024 })).stdout);
85175
+ const atCommit = (await git3(["rev-parse", "--verify", "HEAD"])).trim();
85176
+ if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(atCommit)) throw new Error("history revision is not a full Git object id");
85177
+ const oldCursor = profile.historyRevisionCursor;
85178
+ const same = oldCursor?.atCommit === atCommit;
85179
+ if (same && oldCursor.complete && Date.now() - oldCursor.updatedAt < 60 * 6e4) return { changed: 0, remaining: false };
85180
+ const after = same && !oldCursor.complete ? oldCursor.after : void 0;
85181
+ const page = await cloud.getHistoryOccurrences(projectId, atCommit, after);
85182
+ 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");
85183
+ const parents = (await git3(["rev-list", "--parents", "--max-count=8192", atCommit])).trim().split("\n").filter(Boolean).map((row) => {
85184
+ const [commit, ...parents2] = row.trim().split(/\s+/);
85185
+ return { commit, parents: parents2 };
85186
+ });
85187
+ const reverse = buildReverseSymbolMap({ store, projectId, salt });
85188
+ const ignore2 = loadRepoIgnore(root, profile.ignoreExtra ?? []);
85189
+ const providers = defaultProviders();
85190
+ const parsedFiles = /* @__PURE__ */ new Map();
85191
+ const observations = [];
85192
+ for (const occurrence of page.occurrences) {
85193
+ const anchors = occurrence.quality["historicalAnchors"];
85194
+ const paths = /* @__PURE__ */ new Set(), symbols = [];
85195
+ for (const anchor of anchors ?? []) {
85196
+ const localId = reverse.get(anchor.symbolId), local = localId ? store.getNode(localId) : null;
85197
+ const path2 = local?.attrs["relPath"];
85198
+ if (typeof path2 !== "string" || !pathAllowedByFloor(path2) || isPathBlocked(ignore2, path2) || /[\r\n]/.test(path2)) continue;
85199
+ paths.add(path2);
85200
+ if (!parsedFiles.has(path2)) {
85201
+ const hashes = /* @__PURE__ */ new Map();
85202
+ parsedFiles.set(path2, hashes);
85203
+ const provider = providers.find((p) => p.fileExtensions.includes(extname3(path2).toLowerCase()));
85204
+ if (!provider) continue;
85205
+ const source2 = await git3(["show", `${atCommit}:${path2}`]).catch(() => null);
85206
+ if (source2 === null || Buffer.byteLength(source2) > 1e6 || source2.includes("\0")) continue;
85207
+ await provider.preload?.();
85208
+ for (const symbol2 of provider.extractSymbols(source2, path2)) {
85209
+ const kind = KINDS[symbol2.kind];
85210
+ if (kind && symbol2.bodyHash) hashes.set(projectSymbolId(salt, projectId, path2, symbol2.qname ?? symbol2.name, kind), symbol2.bodyHash);
85211
+ }
85212
+ }
85213
+ const bodyHash = parsedFiles.get(path2).get(anchor.symbolId);
85214
+ if (bodyHash) symbols.push({ symbolId: anchor.symbolId, bodyHash });
85215
+ }
85216
+ const source = occurrenceSourceCommit(occurrence);
85217
+ let diff;
85218
+ if (source && source !== atCommit && paths.size) {
85219
+ const bundle = await buildUnitBundle(root, {
85220
+ sha: atCommit,
85221
+ parents: [source],
85222
+ isMerge: true,
85223
+ index: 0,
85224
+ subIndex: 0,
85225
+ subject: "",
85226
+ body: "",
85227
+ authorDate: "",
85228
+ files: [...paths].map((path2) => ({ path: path2, adds: 1, dels: 1, binary: false }))
85229
+ }, { maxTokens: 4e3, ignorePatterns: profile.ignoreExtra ?? [] }).catch(() => null);
85230
+ if (bundle && !bundle.truncated) diff = bundle.diff;
85231
+ }
85232
+ observations.push({ occurrenceId: occurrence.occurrenceId, symbols, ...diff ? { diff } : {} });
85233
+ }
85234
+ const response = page.occurrences.length ? await cloud.reconcileHistory(projectId, { atCommit, parents, observations }) : { atCommit, receipts: [] };
85235
+ 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");
85236
+ if ((await git3(["rev-parse", "--verify", "HEAD"])).trim() !== atCommit) return { changed: 0, remaining: true };
85237
+ let changed = 0;
85238
+ for (const occurrence of page.occurrences) {
85239
+ const node2 = store.getNode(occurrence.canonicalId) ?? (occurrence.nodeId ? store.getNode(occurrence.nodeId) : null);
85240
+ if (!node2 || node2.attrs["source"] !== "cloud" || node2.attrs["historyQuality"]?.["sourceSupport"] === "quarantined") continue;
85241
+ const receipt = response.receipts.find((r) => r.occurrenceId === occurrence.occurrenceId);
85242
+ const previous = node2.attrs["historyQuality"];
85243
+ if (previous?.["evaluatedAtCommit"] === atCommit && previous["applicability"] === "applicable" && receipt.verdict !== "applicable") continue;
85244
+ store.mergeNode({ ...node2, attrs: { ...node2.attrs, historyQuality: {
85245
+ ...occurrence.quality,
85246
+ evaluatedAtCommit: atCommit,
85247
+ applicability: receipt.verdict,
85248
+ reconciliationEvidence: receipt.evidence
85249
+ } } });
85250
+ changed++;
85251
+ }
85252
+ profile.historyRevisionCursor = { atCommit, ...page.cursor ? { after: page.cursor } : {}, complete: page.cursor === null, updatedAt: Date.now() };
85253
+ (opts.save ?? ((p) => saveProfile(root, p)))(profile);
85254
+ return { changed, remaining: page.cursor !== null };
85255
+ }
85256
+
84614
85257
  // src/project-history.ts
84615
85258
  init_src7();
84616
85259
  init_src();
84617
- import { execFile as execFile4 } from "node:child_process";
85260
+ import { execFile as execFile5 } from "node:child_process";
84618
85261
  import { readFileSync as readFileSync32 } from "node:fs";
84619
85262
  import { join as join37 } from "node:path";
84620
- import { promisify as promisify2 } from "node:util";
85263
+ import { promisify as promisify3 } from "node:util";
84621
85264
 
84622
85265
  // src/history-walk.ts
84623
85266
  var LOCKFILE_NAMES = /* @__PURE__ */ new Set([
@@ -84935,10 +85578,13 @@ function message(u) {
84935
85578
  ${u.body}` : u.subject;
84936
85579
  }
84937
85580
  function narrative(unit, group) {
84938
- if (!group || group.length === 0) return message(unit).trim();
85581
+ return narrativeSources(unit, group).map((source) => source.text).join("\n\n");
85582
+ }
85583
+ function narrativeSources(unit, group) {
85584
+ if (!group || group.length === 0) return [{ commitSha: unit.sha, text: message(unit).trim() }].filter((s) => s.text.length > 0);
84939
85585
  const members = group.filter((g) => g.groupSha === (unit.groupSha ?? unit.sha)).sort((a, b) => a.subIndex - b.subIndex);
84940
85586
  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");
85587
+ return source.map((member) => ({ commitSha: member.sha, text: message(member).trim() })).filter((s) => s.text.length > 0);
84942
85588
  }
84943
85589
 
84944
85590
  // src/git-batch.ts
@@ -85042,13 +85688,116 @@ var GitBlobReader = class {
85042
85688
  }
85043
85689
  };
85044
85690
 
85691
+ // src/history-anchors.ts
85692
+ init_src11();
85693
+ init_src();
85694
+ import { extname as extname4 } from "node:path";
85695
+ var LABELS = {
85696
+ function: "Function",
85697
+ class: "Class",
85698
+ method: "Method",
85699
+ interface: "Interface",
85700
+ const: "Const",
85701
+ enum: "Enum",
85702
+ typealias: "TypeAlias",
85703
+ namespace: "Namespace",
85704
+ property: "Property"
85705
+ };
85706
+ var MAX_SOURCE_BYTES = 1e6;
85707
+ var MAX_ANCHORS = 20;
85708
+ async function historicalAnchors(input) {
85709
+ if (!/^[a-f0-9]{40,64}$/.test(input.unit.sha)) return void 0;
85710
+ const ignore2 = loadRepoIgnore(input.root, input.ignorePatterns);
85711
+ const providers = input.providers ?? defaultProviders();
85712
+ const beforeTokens = /* @__PURE__ */ new Map();
85713
+ for (const line of input.bundle.diff.split("\n")) {
85714
+ const m = /^diff --git a\/(p\d+) b\/(p\d+)$/.exec(line);
85715
+ if (m) beforeTokens.set(m[2], m[1]);
85716
+ }
85717
+ const parsed = /* @__PURE__ */ new Map();
85718
+ const parse4 = (commit, token) => {
85719
+ const path2 = input.bundle.pathTokens[token];
85720
+ if (!path2 || /[\r\n]/.test(path2) || !pathAllowedByFloor(path2) || isPathBlocked(ignore2, path2)) return Promise.resolve(null);
85721
+ const provider = providers.find((p) => p.fileExtensions.includes(extname4(path2).toLowerCase()));
85722
+ if (!provider) return Promise.resolve(null);
85723
+ const spec = `${commit}:${path2}`;
85724
+ if (!parsed.has(spec)) parsed.set(spec, (async () => {
85725
+ try {
85726
+ const source = await input.readBlob(spec);
85727
+ if (source === null || source.includes("\0") || Buffer.byteLength(source) > MAX_SOURCE_BYTES) return null;
85728
+ await provider.preload?.();
85729
+ const symbols = provider.extractSymbols(source, path2).filter((s) => LABELS[s.kind] && s.startByte >= 0 && s.endByte > s.startByte);
85730
+ const offsets = [0];
85731
+ for (let i2 = 0; i2 < source.length; i2++) if (source[i2] === "\n") offsets.push(i2 + 1);
85732
+ offsets.push(source.length + 1);
85733
+ return { symbols, offsets };
85734
+ } catch {
85735
+ return null;
85736
+ }
85737
+ })());
85738
+ return parsed.get(spec);
85739
+ };
85740
+ const out2 = {};
85741
+ const blocks = historySourceBlocks({ narrative: "", diff: input.bundle.diff, commitSha: input.unit.sha });
85742
+ for (const block of blocks) {
85743
+ if (block.kind !== "diff" || !block.pathToken) continue;
85744
+ const lines = block.text.split("\n");
85745
+ const header = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(lines[0]);
85746
+ if (!header) continue;
85747
+ let before = Number(header[1]), after = Number(header[2]);
85748
+ let offset = Buffer.byteLength(lines[0]) + 1;
85749
+ const list = out2[block.pathToken] ?? [];
85750
+ for (const line of lines.slice(1)) {
85751
+ const end = offset + Buffer.byteLength(line);
85752
+ const side = line[0] === "+" ? "after" : line[0] === "-" ? "before" : null;
85753
+ if (side) {
85754
+ const commit = side === "after" ? input.unit.sha : input.unit.parents[0];
85755
+ const token = side === "after" ? block.pathToken : beforeTokens.get(block.pathToken);
85756
+ if (commit && /^[a-f0-9]{40,64}$/.test(commit) && token) {
85757
+ const file2 = await parse4(commit, token);
85758
+ const row = (side === "after" ? after : before) - 1;
85759
+ const start2 = file2?.offsets[row], stop2 = file2?.offsets[row + 1];
85760
+ if (file2 && start2 !== void 0 && stop2 !== void 0) {
85761
+ const symbols = file2.symbols.filter((s) => s.startByte < stop2 && s.endByte > start2).sort((a, b) => a.endByte - a.startByte - (b.endByte - b.startByte));
85762
+ const symbol2 = symbols[0];
85763
+ if (symbol2) {
85764
+ const kind = LABELS[symbol2.kind];
85765
+ const symbolId = projectSymbolId(input.salt, input.projectId, input.bundle.pathTokens[token], symbol2.qname ?? symbol2.name, kind);
85766
+ const prev = list.at(-1);
85767
+ if (prev?.symbolId === symbolId && prev.historical?.side === side && prev.historical.blockDigest === block.digest && prev.historical.endByte + 1 === offset) {
85768
+ prev.historical.endByte = end;
85769
+ } else if (list.length < MAX_ANCHORS) list.push({
85770
+ symbolId,
85771
+ kind,
85772
+ historical: {
85773
+ sourceCommitSha: input.unit.sha,
85774
+ commitSha: commit,
85775
+ side,
85776
+ blockDigest: block.digest,
85777
+ startByte: offset,
85778
+ endByte: end,
85779
+ ...symbol2.bodyHash ? { bodyHash: symbol2.bodyHash } : {}
85780
+ }
85781
+ });
85782
+ }
85783
+ }
85784
+ }
85785
+ }
85786
+ if (line[0] === " " || line[0] === "-") before++;
85787
+ if (line[0] === " " || line[0] === "+") after++;
85788
+ offset = end + 1;
85789
+ }
85790
+ if (list.length) out2[block.pathToken] = list;
85791
+ }
85792
+ return Object.keys(out2).length ? out2 : void 0;
85793
+ }
85794
+
85045
85795
  // src/project-history.ts
85046
85796
  init_profile2();
85047
- var execFileAsync2 = promisify2(execFile4);
85797
+ var execFileAsync2 = promisify3(execFile5);
85048
85798
  var HISTORY_EXTRACTOR_VERSION = "history-2";
85049
85799
  var LEASE_POLL_MS = 2e4;
85050
85800
  var RECOVERY_DEADLINE_MS = 15 * 6e4;
85051
- var MAX_ANCHORS_PER_TOKEN = 20;
85052
85801
  var DEFAULT_LOOKAHEAD = 2;
85053
85802
  async function defaultGit(root, args2) {
85054
85803
  const { stdout } = await execFileAsync2("git", ["-C", root, ...args2], { maxBuffer: 64 * 1024 * 1024, windowsHide: true });
@@ -85099,27 +85848,6 @@ async function spineForUnit(git3, root, unit, readBlob) {
85099
85848
  const wire = (d) => ({ ecosystem: d.ecosystem, name: d.name, version: d.version, kind: d.kind, manifest: d.manifest });
85100
85849
  return { declared: rings.declared.map(wire), context: rings.context.map(wire) };
85101
85850
  }
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
85851
  function stage(profile, patch, now) {
85124
85852
  const at = now().toISOString();
85125
85853
  const prev = profile.historyStage;
@@ -85137,6 +85865,7 @@ function stage(profile, patch, now) {
85137
85865
  // keep showing the error it recovered from); an absent key keeps it.
85138
85866
  ..."lastError" in patch ? patch.lastError ? { lastError: patch.lastError } : {} : prev?.lastError ? { lastError: prev.lastError } : {},
85139
85867
  ...patch.counts ? { counts: patch.counts } : prev?.counts ? { counts: prev.counts } : {},
85868
+ ...patch.coverageGaps ? { coverageGaps: patch.coverageGaps } : prev?.coverageGaps ? { coverageGaps: prev.coverageGaps } : {},
85140
85869
  ...patch.backfillAnchor ? { backfillAnchor: patch.backfillAnchor } : prev?.backfillAnchor ? { backfillAnchor: prev.backfillAnchor } : {}
85141
85870
  }
85142
85871
  };
@@ -85156,7 +85885,7 @@ function unitTiming(postStarted, prepMs, receipt) {
85156
85885
  }
85157
85886
  function addCounts(a, b) {
85158
85887
  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") };
85888
+ return { commitsWalked: sum("commitsWalked"), changesExtracted: sum("changesExtracted"), pairs: sum("pairs"), claims: sum("claims"), anchors: sum("anchors"), packages: sum("packages"), deferredUnits: sum("deferredUnits") };
85160
85889
  }
85161
85890
  async function runHistoryWalk(root, input, client, opts = {}) {
85162
85891
  const reader = opts.git ? null : new GitBlobReader(root);
@@ -85198,7 +85927,12 @@ async function walk10(root, input, client, opts, readBlob) {
85198
85927
  return { profile, processed: 0, state: "refused" };
85199
85928
  }
85200
85929
  if (units.length === 0) {
85201
- profile = stage(profile, { state: "done", watermarkSha: headSha }, now);
85930
+ const deferred = profile.historyStage?.counts?.deferredUnits ?? 0;
85931
+ profile = stage(profile, {
85932
+ state: "done",
85933
+ watermarkSha: headSha,
85934
+ ...deferred > 0 ? { lastError: `${deferred} history units deferred; coverage gaps require source review and re-extraction.` } : {}
85935
+ }, now);
85202
85936
  persist(profile);
85203
85937
  await reportHistoryStage(client, profile, log);
85204
85938
  return { profile, processed: 0, state: "done" };
@@ -85227,7 +85961,8 @@ async function walk10(root, input, client, opts, readBlob) {
85227
85961
  const group = byIndex.get(u.index) ?? [u];
85228
85962
  const bundle = await buildUnitBundle(root, u, {
85229
85963
  ignorePatterns: ignoreLines,
85230
- narrative: narrative(u, u.isMerge ? group : void 0)
85964
+ narrative: narrative(u, u.isMerge ? group : void 0),
85965
+ narrativeSources: narrativeSources(u, u.isMerge ? group : void 0)
85231
85966
  });
85232
85967
  const prev = dryRun ? void 0 : predecessor(k);
85233
85968
  const body2 = {
@@ -85240,6 +85975,7 @@ async function walk10(root, input, client, opts, readBlob) {
85240
85975
  ...u.authorDate ? { authorDate: u.authorDate } : {}
85241
85976
  },
85242
85977
  narrative: bundle.narrative,
85978
+ ...bundle.narrativeSources?.length ? { narrativeSources: bundle.narrativeSources } : {},
85243
85979
  fileList: bundle.fileList,
85244
85980
  diff: bundle.diff,
85245
85981
  pathTokens: Object.keys(bundle.pathTokens),
@@ -85251,7 +85987,15 @@ async function walk10(root, input, client, opts, readBlob) {
85251
85987
  };
85252
85988
  const spine = await spineForUnit(git3, root, u, readBlob);
85253
85989
  if (spine) body2.spine = spine;
85254
- const anchors = anchorsFor(bundle.pathTokens, ignoreLines, projectId, opts.anchors);
85990
+ const anchors = opts.anchors ? await historicalAnchors({
85991
+ root,
85992
+ projectId,
85993
+ salt: opts.anchors.salt,
85994
+ unit: u,
85995
+ bundle,
85996
+ ignorePatterns: ignoreLines,
85997
+ readBlob
85998
+ }) : void 0;
85255
85999
  if (anchors) body2.anchors = anchors;
85256
86000
  return { body: body2, prepMs: Date.now() - prepStarted };
85257
86001
  })();
@@ -85384,6 +86128,15 @@ async function walk10(root, input, client, opts, readBlob) {
85384
86128
  return { profile, processed, state: "refused" };
85385
86129
  case "rejected":
85386
86130
  case "error":
86131
+ if (outcome.status === "error" && outcome.parked && outcome.runId && ["deferred-context", "deferred-budget"].includes(outcome.errorCode ?? "")) {
86132
+ const gaps = [...profile.historyStage?.coverageGaps ?? []];
86133
+ const known = gaps.some((g) => g.runId === outcome.runId);
86134
+ counts = addCounts(counts, { commitsWalked: 1, deferredUnits: known ? 0 : 1 });
86135
+ if (!known) gaps.push({ sha: u.sha, subIndex: u.subIndex, runId: outcome.runId, reason: outcome.errorCode });
86136
+ profile = stage(profile, { coverageGaps: gaps.slice(-1e3) }, now);
86137
+ log(`[errata] history ${label}: deferred (${outcome.errorCode}); coverage gap retained as ${outcome.runId}`);
86138
+ break;
86139
+ }
85387
86140
  profile = stage(profile, { state: "paused", lastError: `unit ${label} ${outcome.status}: ${outcome.error.slice(0, 200)}` }, now);
85388
86141
  persist(profile);
85389
86142
  await reportHistoryStage(client, profile, log);
@@ -85394,7 +86147,11 @@ async function walk10(root, input, client, opts, readBlob) {
85394
86147
  processed += 1;
85395
86148
  }
85396
86149
  const reachedHead = (profile.historyStage?.unitsDone ?? 0) >= total;
85397
- profile = stage(profile, reachedHead ? { state: "done", watermarkSha: headSha } : { state: dryRun ? "extracting" : "contributing" }, now);
86150
+ profile = stage(profile, reachedHead ? {
86151
+ state: "done",
86152
+ watermarkSha: headSha,
86153
+ ...(counts.deferredUnits ?? 0) > 0 ? { lastError: `${counts.deferredUnits} history units deferred; coverage gaps require source review and re-extraction.` } : {}
86154
+ } : { state: dryRun ? "extracting" : "contributing" }, now);
85398
86155
  persist(profile);
85399
86156
  await reportHistoryStage(client, profile, log);
85400
86157
  return { profile, processed, state: profile.historyStage?.state ?? "done" };
@@ -85477,7 +86234,7 @@ async function startHistoryStage(root, input, client, walk11 = {}) {
85477
86234
 
85478
86235
  // src/link-flow.ts
85479
86236
  init_cloud_auth();
85480
- import { createHash as createHash17 } from "node:crypto";
86237
+ import { createHash as createHash18 } from "node:crypto";
85481
86238
  import { execFileSync as execFileSync5 } from "node:child_process";
85482
86239
  import { existsSync as existsSync30, writeFileSync as writeFileSync23 } from "node:fs";
85483
86240
  import { join as join38 } from "node:path";
@@ -85771,7 +86528,7 @@ async function loginOAuthLoopback(opts) {
85771
86528
 
85772
86529
  // src/link-flow.ts
85773
86530
  function remoteFingerprint(locator) {
85774
- return createHash17("sha256").update(locator).digest("hex");
86531
+ return createHash18("sha256").update(locator).digest("hex");
85775
86532
  }
85776
86533
  function isDecision(value) {
85777
86534
  if (!value || typeof value !== "object") return false;
@@ -87057,13 +87814,24 @@ async function startMultiDaemon(opts = {}) {
87057
87814
  });
87058
87815
  const pullProjectAnchors = async (r, client, salt) => {
87059
87816
  const donePass = markPass(`anchor-pull:${r.engine.profile.name}`);
87817
+ const beforeCorrections = r.engine.profile.projectHistoryCorrectionCursor ?? 0;
87060
87818
  const sync = await syncProjectAnchors({
87061
87819
  root: r.root,
87062
87820
  profile: r.engine.profile,
87063
87821
  store: r.engine.store,
87822
+ correctionMirrors: [sharedStore],
87064
87823
  cloud: client,
87065
87824
  salt
87066
87825
  }).finally(donePass);
87826
+ if ((r.engine.profile.projectHistoryCorrectionCursor ?? 0) > beforeCorrections) {
87827
+ for (const record2 of records) record2.engine.markContextDirty();
87828
+ }
87829
+ try {
87830
+ const revision = await syncHistoryRevision({ root: r.root, profile: r.engine.profile, store: r.engine.store, cloud: client, salt });
87831
+ if (revision.changed) r.engine.markContextDirty();
87832
+ } catch (err2) {
87833
+ console.warn(`[errata] history revision reconciliation failed for ${r.engine.profile.name}: ${err2 instanceof Error ? err2.message : String(err2)}`);
87834
+ }
87067
87835
  if (sync.synced) {
87068
87836
  if (sync.anchors + sync.updated > 0) {
87069
87837
  r.engine.markContextDirty();