@jinn-network/core 0.1.0-canary.3afae198

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.
Files changed (155) hide show
  1. package/dist/canonical-json.d.ts +1 -0
  2. package/dist/canonical-json.js +67 -0
  3. package/dist/captured-task.d.ts +140 -0
  4. package/dist/captured-task.js +122 -0
  5. package/dist/contribution-store.d.ts +97 -0
  6. package/dist/contribution-store.js +770 -0
  7. package/dist/corpus-read/acquire.d.ts +39 -0
  8. package/dist/corpus-read/acquire.js +212 -0
  9. package/dist/corpus-read/cache.d.ts +14 -0
  10. package/dist/corpus-read/cache.js +17 -0
  11. package/dist/corpus-read/capture-meta.d.ts +16 -0
  12. package/dist/corpus-read/capture-meta.js +18 -0
  13. package/dist/corpus-read/create-corpus.d.ts +2 -0
  14. package/dist/corpus-read/create-corpus.js +97 -0
  15. package/dist/corpus-read/fetch-artifact.d.ts +21 -0
  16. package/dist/corpus-read/fetch-artifact.js +32 -0
  17. package/dist/corpus-read/fetch.d.ts +12 -0
  18. package/dist/corpus-read/fetch.js +24 -0
  19. package/dist/corpus-read/http-discovery.d.ts +9 -0
  20. package/dist/corpus-read/http-discovery.js +128 -0
  21. package/dist/corpus-read/index.d.ts +10 -0
  22. package/dist/corpus-read/index.js +10 -0
  23. package/dist/corpus-read/ipfs.d.ts +14 -0
  24. package/dist/corpus-read/ipfs.js +81 -0
  25. package/dist/corpus-read/route-resolver.d.ts +16 -0
  26. package/dist/corpus-read/route-resolver.js +19 -0
  27. package/dist/corpus-read/types.d.ts +177 -0
  28. package/dist/corpus-read/types.js +42 -0
  29. package/dist/envelope.d.ts +134 -0
  30. package/dist/envelope.js +162 -0
  31. package/dist/evidence-adapter.d.ts +26 -0
  32. package/dist/evidence-adapter.js +321 -0
  33. package/dist/evidence-filesystem.d.ts +40 -0
  34. package/dist/evidence-filesystem.js +267 -0
  35. package/dist/evidence-index.d.ts +117 -0
  36. package/dist/evidence-index.js +1083 -0
  37. package/dist/evidence-store-lock.d.ts +2 -0
  38. package/dist/evidence-store-lock.js +208 -0
  39. package/dist/execution-envelope.d.ts +3142 -0
  40. package/dist/execution-envelope.js +203 -0
  41. package/dist/index.d.ts +12 -0
  42. package/dist/index.js +12 -0
  43. package/dist/manifest.d.ts +75 -0
  44. package/dist/manifest.js +184 -0
  45. package/dist/paired.d.ts +68 -0
  46. package/dist/paired.js +92 -0
  47. package/dist/scrub/apply-dispositions.d.ts +74 -0
  48. package/dist/scrub/apply-dispositions.js +309 -0
  49. package/dist/scrub/build.d.ts +75 -0
  50. package/dist/scrub/build.js +143 -0
  51. package/dist/scrub/checksummed-instruments-detector.d.ts +13 -0
  52. package/dist/scrub/checksummed-instruments-detector.js +119 -0
  53. package/dist/scrub/data/bip39-english.d.ts +2 -0
  54. package/dist/scrub/data/bip39-english.js +2051 -0
  55. package/dist/scrub/data/gitleaks-rules.d.ts +56 -0
  56. package/dist/scrub/data/gitleaks-rules.js +67 -0
  57. package/dist/scrub/emit-scrub.d.ts +52 -0
  58. package/dist/scrub/emit-scrub.js +122 -0
  59. package/dist/scrub/eval/findings-from-scrub.d.ts +18 -0
  60. package/dist/scrub/eval/findings-from-scrub.js +99 -0
  61. package/dist/scrub/eval/fixtures.d.ts +14 -0
  62. package/dist/scrub/eval/fixtures.js +159 -0
  63. package/dist/scrub/eval/index.d.ts +6 -0
  64. package/dist/scrub/eval/index.js +6 -0
  65. package/dist/scrub/eval/local-corpus.d.ts +15 -0
  66. package/dist/scrub/eval/local-corpus.js +29 -0
  67. package/dist/scrub/eval/metrics.d.ts +19 -0
  68. package/dist/scrub/eval/metrics.js +77 -0
  69. package/dist/scrub/eval/run-bench.d.ts +8 -0
  70. package/dist/scrub/eval/run-bench.js +125 -0
  71. package/dist/scrub/eval/types.d.ts +69 -0
  72. package/dist/scrub/eval/types.js +7 -0
  73. package/dist/scrub/finding.d.ts +38 -0
  74. package/dist/scrub/finding.js +7 -0
  75. package/dist/scrub/git-identity-detector.d.ts +16 -0
  76. package/dist/scrub/git-identity-detector.js +116 -0
  77. package/dist/scrub/gitleaks-detector.d.ts +13 -0
  78. package/dist/scrub/gitleaks-detector.js +45 -0
  79. package/dist/scrub/gliner-detector.d.ts +65 -0
  80. package/dist/scrub/gliner-detector.js +129 -0
  81. package/dist/scrub/index.d.ts +26 -0
  82. package/dist/scrub/index.js +26 -0
  83. package/dist/scrub/ip-address-detector.d.ts +12 -0
  84. package/dist/scrub/ip-address-detector.js +81 -0
  85. package/dist/scrub/key-policy.d.ts +30 -0
  86. package/dist/scrub/key-policy.js +72 -0
  87. package/dist/scrub/known-identity-detector.d.ts +66 -0
  88. package/dist/scrub/known-identity-detector.js +284 -0
  89. package/dist/scrub/layer2.d.ts +18 -0
  90. package/dist/scrub/layer2.js +28 -0
  91. package/dist/scrub/ml-pii-stage.d.ts +42 -0
  92. package/dist/scrub/ml-pii-stage.js +119 -0
  93. package/dist/scrub/pii-build.d.ts +47 -0
  94. package/dist/scrub/pii-build.js +67 -0
  95. package/dist/scrub/pipeline.d.ts +77 -0
  96. package/dist/scrub/pipeline.js +225 -0
  97. package/dist/scrub/plain-patterns-stage.d.ts +33 -0
  98. package/dist/scrub/plain-patterns-stage.js +88 -0
  99. package/dist/scrub/policy.d.ts +28 -0
  100. package/dist/scrub/policy.js +156 -0
  101. package/dist/scrub/provenance.d.ts +53 -0
  102. package/dist/scrub/provenance.js +83 -0
  103. package/dist/scrub/reject-classes-detector.d.ts +11 -0
  104. package/dist/scrub/reject-classes-detector.js +195 -0
  105. package/dist/scrub/reject-publish-error.d.ts +30 -0
  106. package/dist/scrub/reject-publish-error.js +47 -0
  107. package/dist/scrub/review-queue.d.ts +90 -0
  108. package/dist/scrub/review-queue.js +259 -0
  109. package/dist/scrub/secretlint-stage.d.ts +21 -0
  110. package/dist/scrub/secretlint-stage.js +264 -0
  111. package/dist/scrub/transformers-detector.d.ts +41 -0
  112. package/dist/scrub/transformers-detector.js +77 -0
  113. package/dist/scrub/types.d.ts +36 -0
  114. package/dist/scrub/types.js +1 -0
  115. package/dist/scrub/url-credentials-detector.d.ts +9 -0
  116. package/dist/scrub/url-credentials-detector.js +66 -0
  117. package/dist/session-provenance.d.ts +75 -0
  118. package/dist/session-provenance.js +20 -0
  119. package/dist/skill-artifact.d.ts +442 -0
  120. package/dist/skill-artifact.js +136 -0
  121. package/dist/trajectory/hash-chain.d.ts +18 -0
  122. package/dist/trajectory/hash-chain.js +47 -0
  123. package/dist/trajectory/index.d.ts +4 -0
  124. package/dist/trajectory/index.js +4 -0
  125. package/dist/trajectory/schema.d.ts +681 -0
  126. package/dist/trajectory/schema.js +119 -0
  127. package/dist/trajectory/transcript-parsers/aider-history.d.ts +42 -0
  128. package/dist/trajectory/transcript-parsers/aider-history.js +181 -0
  129. package/dist/trajectory/transcript-parsers/claude-code-jsonl.d.ts +37 -0
  130. package/dist/trajectory/transcript-parsers/claude-code-jsonl.js +142 -0
  131. package/dist/trajectory/transcript-parsers/codex-session.d.ts +42 -0
  132. package/dist/trajectory/transcript-parsers/codex-session.js +235 -0
  133. package/dist/trajectory/transcript-parsers/continue-devdata.d.ts +41 -0
  134. package/dist/trajectory/transcript-parsers/continue-devdata.js +190 -0
  135. package/dist/trajectory/transcript-parsers/cursor-sqlite.d.ts +38 -0
  136. package/dist/trajectory/transcript-parsers/cursor-sqlite.js +118 -0
  137. package/dist/trajectory/transcript-parsers/gemini-session.d.ts +35 -0
  138. package/dist/trajectory/transcript-parsers/gemini-session.js +137 -0
  139. package/dist/trajectory/transcript-parsers/index.d.ts +7 -0
  140. package/dist/trajectory/transcript-parsers/index.js +7 -0
  141. package/dist/trajectory/transcript-parsers/types.d.ts +101 -0
  142. package/dist/trajectory/transcript-parsers/types.js +32 -0
  143. package/dist/trajectory/transcript-to-spans/attrs.d.ts +4 -0
  144. package/dist/trajectory/transcript-to-spans/attrs.js +30 -0
  145. package/dist/trajectory/transcript-to-spans/claude-code-stream-json.d.ts +8 -0
  146. package/dist/trajectory/transcript-to-spans/claude-code-stream-json.js +162 -0
  147. package/dist/trajectory/transcript-to-spans/codex-exec-json.d.ts +8 -0
  148. package/dist/trajectory/transcript-to-spans/codex-exec-json.js +253 -0
  149. package/dist/trajectory/transcript-to-spans/index.d.ts +4 -0
  150. package/dist/trajectory/transcript-to-spans/index.js +4 -0
  151. package/dist/trajectory/transcript-to-spans/types.d.ts +18 -0
  152. package/dist/trajectory/transcript-to-spans/types.js +1 -0
  153. package/dist/window.d.ts +12 -0
  154. package/dist/window.js +5 -0
  155. package/package.json +72 -0
@@ -0,0 +1,19 @@
1
+ import type { ClassCounts, ClassMetrics, ScrubClass } from './types.js';
2
+ export declare function emptyCounts(): ClassCounts;
3
+ export declare function metricsFromCounts(c: ClassCounts): ClassMetrics;
4
+ export declare function mergeCounts(a: ClassCounts, b: ClassCounts): ClassCounts;
5
+ /**
6
+ * Score predicted spans against gold labels for one class using interval overlap.
7
+ * A prediction that overlaps any unmatched gold label is TP; unmatched gold → FN;
8
+ * unmatched predictions → FP. Distinct-source dedupe: gold labels with the same
9
+ * `sourceId` count once (first occurrence wins).
10
+ */
11
+ export declare function scoreClass(gold: Array<{
12
+ start: number;
13
+ end: number;
14
+ sourceId?: string;
15
+ }>, pred: Array<{
16
+ start: number;
17
+ end: number;
18
+ }>): ClassCounts;
19
+ export declare function aggregateClassMap(perFixture: Array<Partial<Record<ScrubClass, ClassCounts>>>): Partial<Record<ScrubClass, ClassMetrics>>;
@@ -0,0 +1,77 @@
1
+ export function emptyCounts() {
2
+ return { tp: 0, fp: 0, fn: 0 };
3
+ }
4
+ export function metricsFromCounts(c) {
5
+ const recall = c.tp + c.fn === 0 ? 1 : c.tp / (c.tp + c.fn);
6
+ const precision = c.tp + c.fp === 0 ? 1 : c.tp / (c.tp + c.fp);
7
+ const beta = 2;
8
+ const betaSq = beta * beta;
9
+ const fBeta2 = precision + recall === 0
10
+ ? 0
11
+ : ((1 + betaSq) * precision * recall) / (betaSq * precision + recall);
12
+ return { ...c, recall, precision, fBeta2 };
13
+ }
14
+ export function mergeCounts(a, b) {
15
+ return { tp: a.tp + b.tp, fp: a.fp + b.fp, fn: a.fn + b.fn };
16
+ }
17
+ /**
18
+ * Score predicted spans against gold labels for one class using interval overlap.
19
+ * A prediction that overlaps any unmatched gold label is TP; unmatched gold → FN;
20
+ * unmatched predictions → FP. Distinct-source dedupe: gold labels with the same
21
+ * `sourceId` count once (first occurrence wins).
22
+ */
23
+ export function scoreClass(gold, pred) {
24
+ const seenSources = new Set();
25
+ const dedupedGold = [];
26
+ for (const g of gold) {
27
+ if (g.sourceId) {
28
+ if (seenSources.has(g.sourceId))
29
+ continue;
30
+ seenSources.add(g.sourceId);
31
+ }
32
+ dedupedGold.push({ start: g.start, end: g.end });
33
+ }
34
+ const goldMatched = new Array(dedupedGold.length).fill(false);
35
+ const predMatched = new Array(pred.length).fill(false);
36
+ for (let pi = 0; pi < pred.length; pi += 1) {
37
+ for (let gi = 0; gi < dedupedGold.length; gi += 1) {
38
+ if (goldMatched[gi])
39
+ continue;
40
+ if (overlaps(pred[pi], dedupedGold[gi])) {
41
+ goldMatched[gi] = true;
42
+ predMatched[pi] = true;
43
+ break;
44
+ }
45
+ }
46
+ }
47
+ let tp = 0;
48
+ let fn = 0;
49
+ let fp = 0;
50
+ for (const m of goldMatched) {
51
+ if (m)
52
+ tp += 1;
53
+ else
54
+ fn += 1;
55
+ }
56
+ for (const m of predMatched) {
57
+ if (!m)
58
+ fp += 1;
59
+ }
60
+ return { tp, fp, fn };
61
+ }
62
+ function overlaps(a, b) {
63
+ return a.start < b.end && b.start < a.end;
64
+ }
65
+ export function aggregateClassMap(perFixture) {
66
+ const acc = {};
67
+ for (const row of perFixture) {
68
+ for (const [cls, counts] of Object.entries(row)) {
69
+ acc[cls] = acc[cls] ? mergeCounts(acc[cls], counts) : { ...counts };
70
+ }
71
+ }
72
+ const out = {};
73
+ for (const [cls, counts] of Object.entries(acc)) {
74
+ out[cls] = metricsFromCounts(counts);
75
+ }
76
+ return out;
77
+ }
@@ -0,0 +1,8 @@
1
+ import type { ScrubPipeline } from '../pipeline.js';
2
+ import type { BenchReport, ClassCounts, EvalFixture, ScrubClass } from './types.js';
3
+ export declare function pipelineForProfile(profile: EvalFixture['profile'], fixture?: EvalFixture): ScrubPipeline;
4
+ export declare function scoreFixture(fixture: EvalFixture): Promise<{
5
+ counts: Partial<Record<ScrubClass, ClassCounts>>;
6
+ corruptionFailure: boolean;
7
+ }>;
8
+ export declare function runBench(fixtures: EvalFixture[]): Promise<BenchReport>;
@@ -0,0 +1,125 @@
1
+ import { buildLayer2ScrubPipeline } from '../layer2.js';
2
+ import { buildScrubPipeline, buildSeedScrubPipeline } from '../build.js';
3
+ import { RejectPublishError } from '../reject-publish-error.js';
4
+ import { UnresolvedFlagError } from '../review-queue.js';
5
+ import { aggregateClassMap, emptyCounts, scoreClass } from './metrics.js';
6
+ import { findingsFromScrubResult } from './findings-from-scrub.js';
7
+ import { createMemoryReviewQueueStore } from '../review-queue.js';
8
+ export function pipelineForProfile(profile, fixture) {
9
+ const knownIdentity = fixture?.identityPack || fixture?.allowlist
10
+ ? { pack: fixture.identityPack, allowlist: fixture.allowlist }
11
+ : undefined;
12
+ // Bench must not fail-closed on flags (metrics need the scrubbed text +
13
+ // unresolvedFlags); use an in-memory review store and disable abort.
14
+ const reviewOpts = {
15
+ reviewStore: createMemoryReviewQueueStore(),
16
+ failClosedOnUnresolvedFlags: false,
17
+ };
18
+ switch (profile ?? 'seed') {
19
+ case 'trace':
20
+ return buildScrubPipeline({ knownIdentity, ...reviewOpts });
21
+ case 'layer2':
22
+ return buildLayer2ScrubPipeline({ knownIdentity });
23
+ case 'seed':
24
+ default:
25
+ return buildSeedScrubPipeline({ knownIdentity, ...reviewOpts });
26
+ }
27
+ }
28
+ export async function scoreFixture(fixture) {
29
+ const pipeline = pipelineForProfile(fixture.profile, fixture);
30
+ const key = fixture.key ?? 'content';
31
+ let scrubbed;
32
+ let redactions = [];
33
+ let rejectClass;
34
+ try {
35
+ const result = await pipeline.run({ [key]: fixture.text });
36
+ scrubbed = String(result.attributes[key] ?? '');
37
+ redactions = result.redactions;
38
+ }
39
+ catch (err) {
40
+ if (err instanceof RejectPublishError) {
41
+ // Reject-publish aborts without redacting the span — count as a hit on
42
+ // the labeled class (or the error's class when unlabeled).
43
+ rejectClass = err.scrubClass;
44
+ scrubbed = fixture.text;
45
+ redactions = [];
46
+ }
47
+ else if (err instanceof UnresolvedFlagError) {
48
+ // Flag-hold aborts publish; for bench metrics treat as a class hit on
49
+ // the first flagged finding (flag-or-redact recall).
50
+ rejectClass = err.findings[0]?.class;
51
+ scrubbed = fixture.text;
52
+ redactions = [];
53
+ }
54
+ else {
55
+ throw err;
56
+ }
57
+ }
58
+ if (fixture.mustSurvive) {
59
+ return {
60
+ counts: {},
61
+ corruptionFailure: scrubbed !== fixture.text || rejectClass !== undefined,
62
+ };
63
+ }
64
+ if (rejectClass) {
65
+ const counts = {};
66
+ const labeled = fixture.labels.filter((l) => l.class === rejectClass);
67
+ if (labeled.length > 0) {
68
+ counts[rejectClass] = scoreClass(labeled, labeled.map((l) => ({ class: rejectClass, start: l.start, end: l.end })));
69
+ }
70
+ else {
71
+ counts[rejectClass] = { tp: 0, fp: 1, fn: 0 };
72
+ }
73
+ for (const l of fixture.labels) {
74
+ if (!counts[l.class])
75
+ counts[l.class] = emptyCounts();
76
+ if (l.class !== rejectClass) {
77
+ // Other labeled classes missed because publish aborted first.
78
+ counts[l.class] = scoreClass([l], []);
79
+ }
80
+ }
81
+ return { counts, corruptionFailure: false };
82
+ }
83
+ const findings = findingsFromScrubResult(fixture, scrubbed, redactions);
84
+ const classes = new Set([
85
+ ...fixture.labels.map((l) => l.class),
86
+ ...findings.map((f) => f.class),
87
+ ]);
88
+ const counts = {};
89
+ for (const cls of classes) {
90
+ const gold = fixture.labels.filter((l) => l.class === cls);
91
+ const pred = findings.filter((f) => f.class === cls);
92
+ counts[cls] = scoreClass(gold, pred);
93
+ }
94
+ for (const l of fixture.labels) {
95
+ if (!counts[l.class])
96
+ counts[l.class] = emptyCounts();
97
+ }
98
+ return { counts, corruptionFailure: false };
99
+ }
100
+ export async function runBench(fixtures) {
101
+ const started = Date.now();
102
+ const perFixture = [];
103
+ let corruptionFixtures = 0;
104
+ let corruptionFailures = 0;
105
+ const profiles = new Set();
106
+ for (const fixture of fixtures) {
107
+ profiles.add(fixture.profile ?? 'seed');
108
+ if (fixture.mustSurvive) {
109
+ corruptionFixtures += 1;
110
+ const { corruptionFailure } = await scoreFixture(fixture);
111
+ if (corruptionFailure)
112
+ corruptionFailures += 1;
113
+ continue;
114
+ }
115
+ const { counts } = await scoreFixture(fixture);
116
+ perFixture.push(counts);
117
+ }
118
+ return {
119
+ schemaVersion: 1,
120
+ profiles: [...profiles].sort(),
121
+ classes: aggregateClassMap(perFixture),
122
+ corruption: { fixtures: corruptionFixtures, failures: corruptionFailures },
123
+ elapsedMs: Date.now() - started,
124
+ };
125
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Scrub eval harness types (#1968 / design §7).
3
+ *
4
+ * Synthetic fixtures and operator-local labels never leave the machine as text
5
+ * in published artifacts — only metrics-only JSON (counts) is safe to share.
6
+ */
7
+ export type { ScrubClass } from '../finding.js';
8
+ import type { ScrubClass } from '../finding.js';
9
+ export interface LabeledSpan {
10
+ class: ScrubClass;
11
+ /** Inclusive start offset in the fixture text. */
12
+ start: number;
13
+ /** Exclusive end offset. */
14
+ end: number;
15
+ /** Optional stable id for distinct-source weighting (dedupe replayed boilerplate). */
16
+ sourceId?: string;
17
+ }
18
+ export interface EvalFixture {
19
+ id: string;
20
+ /** Attribute key the pipeline sees (default `content`). */
21
+ key?: string;
22
+ text: string;
23
+ labels: LabeledSpan[];
24
+ /**
25
+ * When true, the scrubbed output must be byte-identical to the input
26
+ * (corruption corpus / #1784 clean prose).
27
+ */
28
+ mustSurvive?: boolean;
29
+ /** Profile to run: trace (strict), seed, or layer2. Default seed for CI. */
30
+ profile?: 'trace' | 'seed' | 'layer2';
31
+ /**
32
+ * Optional known-identity pack for #1971 fixtures (self-handle exact match).
33
+ * When set, the seed/trace/layer2 builder injects the pack into the inventory.
34
+ */
35
+ identityPack?: import('../known-identity-detector.js').KnownIdentityPack;
36
+ /** Optional extra allowlist entries for #1971 fixtures. */
37
+ allowlist?: import('../known-identity-detector.js').InstanceAllowlist;
38
+ }
39
+ export interface ClassCounts {
40
+ tp: number;
41
+ fp: number;
42
+ fn: number;
43
+ }
44
+ export interface ClassMetrics extends ClassCounts {
45
+ recall: number;
46
+ precision: number;
47
+ /** Fβ with β=2 (recall-weighted). */
48
+ fBeta2: number;
49
+ }
50
+ export interface BenchReport {
51
+ /** Schema version for metrics-only artifacts. */
52
+ schemaVersion: 1;
53
+ /** Which pipeline builders were measured. */
54
+ profiles: string[];
55
+ /** Per-class counts + rates. No text, no spans. */
56
+ classes: Partial<Record<ScrubClass, ClassMetrics>>;
57
+ /** Corruption corpus: fixtures that must pass through unchanged. */
58
+ corruption: {
59
+ fixtures: number;
60
+ failures: number;
61
+ };
62
+ /** Wall time for the run (ms). */
63
+ elapsedMs: number;
64
+ }
65
+ export interface ScoredFinding {
66
+ class: ScrubClass;
67
+ start: number;
68
+ end: number;
69
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Scrub eval harness types (#1968 / design §7).
3
+ *
4
+ * Synthetic fixtures and operator-local labels never leave the machine as text
5
+ * in published artifacts — only metrics-only JSON (counts) is safe to share.
6
+ */
7
+ export {};
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Detector finding contract (#1969 / design §6.1).
3
+ *
4
+ * Detectors emit findings; they never edit attributes. Disposition is applied
5
+ * in one pass from the versioned policy table (§6.5).
6
+ */
7
+ /** Class taxonomy from design §3.2. */
8
+ export type ScrubClass = 'A1' | 'A2' | 'A3' | 'A4' | 'A5' | 'B1' | 'B2' | 'B3' | 'B4' | 'B5' | 'B6' | 'B7' | 'C1' | 'C2' | 'D1' | 'D2' | 'D3' | 'E1';
9
+ /** DLP-style confidence bands (design §6.1). */
10
+ export type Band = 'VERY_LOW' | 'LOW' | 'MEDIUM' | 'HIGH' | 'VERY_HIGH';
11
+ export interface FindingSpan {
12
+ /** Attribute key the span lives in. */
13
+ key: string;
14
+ /** Inclusive start offset in the string value. */
15
+ start: number;
16
+ /** Exclusive end offset. */
17
+ end: number;
18
+ }
19
+ export interface Finding {
20
+ class: ScrubClass;
21
+ span: FindingSpan;
22
+ confidence: Band;
23
+ /** Explainability: which shape/checksum/context/model fired. */
24
+ evidence: string[];
25
+ detector: {
26
+ name: string;
27
+ version: string;
28
+ };
29
+ }
30
+ /**
31
+ * Detector contract: emit findings over an attribute bag. Must not mutate
32
+ * values — disposition owns edits.
33
+ */
34
+ export interface Detector {
35
+ readonly name: string;
36
+ readonly version: string;
37
+ detect(attributes: Record<string, unknown>): Finding[] | Promise<Finding[]>;
38
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Detector finding contract (#1969 / design §6.1).
3
+ *
4
+ * Detectors emit findings; they never edit attributes. Disposition is applied
5
+ * in one pass from the versioned policy table (§6.5).
6
+ */
7
+ export {};
@@ -0,0 +1,16 @@
1
+ /**
2
+ * git-identity detector — deterministic B2 findings for personal names in
3
+ * structured git carriers (#1970 / design §3.2 B2, §6.2):
4
+ *
5
+ * - `Author:` / `Committer:` / `Co-Authored-By:` / `Signed-off-by:` lines
6
+ * - `git config user.name` / `git config user.email` (and `user.name=` /
7
+ * `user.email=` config-list forms)
8
+ *
9
+ * Emits name (and email-on-user.email carriers) as B2 VERY_HIGH. Emails on
10
+ * Author-style trailers stay with the B1 plain-patterns detector so spans do
11
+ * not overlap; both still redact on the same line (joint effect). Free-prose
12
+ * names outside these carriers are untouched.
13
+ */
14
+ import { type KeyPolicy } from './key-policy.js';
15
+ import type { Detector } from './finding.js';
16
+ export declare function gitIdentityDetector(policy: KeyPolicy): Detector;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * git-identity detector — deterministic B2 findings for personal names in
3
+ * structured git carriers (#1970 / design §3.2 B2, §6.2):
4
+ *
5
+ * - `Author:` / `Committer:` / `Co-Authored-By:` / `Signed-off-by:` lines
6
+ * - `git config user.name` / `git config user.email` (and `user.name=` /
7
+ * `user.email=` config-list forms)
8
+ *
9
+ * Emits name (and email-on-user.email carriers) as B2 VERY_HIGH. Emails on
10
+ * Author-style trailers stay with the B1 plain-patterns detector so spans do
11
+ * not overlap; both still redact on the same line (joint effect). Free-prose
12
+ * names outside these carriers are untouched.
13
+ */
14
+ import { classifyKey } from './key-policy.js';
15
+ const VERSION = '0.1.0';
16
+ /** Trailer labels that carry a display name (optional `<email>`). */
17
+ const TRAILER_LABEL = /^(Author|Committer|Co-Authored-By|Signed-off-by):\s*/gim;
18
+ /**
19
+ * `git config [--global|--local|…] user.name VALUE` — VALUE may be quoted.
20
+ * Captures only the value, not the carrier keywords.
21
+ */
22
+ const GIT_CONFIG_NAME = /\bgit\s+config\s+(?:--\w+\s+)*user\.name\s+(?:"([^"\n]+)"|'([^'\n]+)'|(\S+))/gi;
23
+ /** Same shape for `user.email`. */
24
+ const GIT_CONFIG_EMAIL = /\bgit\s+config\s+(?:--\w+\s+)*user\.email\s+(?:"([^"\n]+)"|'([^'\n]+)'|(\S+))/gi;
25
+ /** `git config --list` / env-dump style: `user.name=…` at line start. */
26
+ const USER_NAME_ASSIGN = /^user\.name\s*=\s*(?:"([^"\n]+)"|'([^'\n]+)'|([^\n]+?))\s*$/gim;
27
+ const USER_EMAIL_ASSIGN = /^user\.email\s*=\s*(?:"([^"\n]+)"|'([^'\n]+)'|([^\n]+?))\s*$/gim;
28
+ function firstCapture(match) {
29
+ for (let i = 1; i < match.length; i += 1) {
30
+ const g = match[i];
31
+ if (g !== undefined && g.length > 0)
32
+ return { value: g, groupIndex: i };
33
+ }
34
+ return null;
35
+ }
36
+ function captureOffset(match, groupIndex) {
37
+ // Reconstruct start of the chosen capture group within match[0].
38
+ // Groups are alternatives — only one is set — so search the full match text.
39
+ const full = match[0];
40
+ const value = match[groupIndex];
41
+ const at = full.lastIndexOf(value);
42
+ return match.index + (at >= 0 ? at : 0);
43
+ }
44
+ function pushFinding(findings, key, start, end, evidence, detector) {
45
+ if (start < 0 || end <= start)
46
+ return;
47
+ findings.push({
48
+ class: 'B2',
49
+ span: { key, start, end },
50
+ confidence: 'VERY_HIGH',
51
+ evidence: [evidence],
52
+ detector,
53
+ });
54
+ }
55
+ function collectTrailers(text, key, detector) {
56
+ const findings = [];
57
+ const re = new RegExp(TRAILER_LABEL.source, TRAILER_LABEL.flags);
58
+ let match;
59
+ while ((match = re.exec(text)) !== null) {
60
+ const valueStart = match.index + match[0].length;
61
+ const lineEnd = text.indexOf('\n', valueStart);
62
+ const end = lineEnd < 0 ? text.length : lineEnd;
63
+ const rest = text.slice(valueStart, end);
64
+ // Prefer `Name <email>` — redact name only (email → B1).
65
+ const angle = /^(.*?)\s*<[^>\n]*>\s*$/.exec(rest);
66
+ if (angle) {
67
+ const rawName = angle[1] ?? '';
68
+ const name = rawName.trim();
69
+ if (name.length > 0) {
70
+ const leading = rawName.length - rawName.trimStart().length;
71
+ const start = valueStart + leading;
72
+ pushFinding(findings, key, start, start + name.length, 'git-identity:trailer-name', detector);
73
+ }
74
+ continue;
75
+ }
76
+ const name = rest.trim();
77
+ if (name.length > 0) {
78
+ const leading = rest.length - rest.trimStart().length;
79
+ const start = valueStart + leading;
80
+ pushFinding(findings, key, start, start + name.length, 'git-identity:trailer-name', detector);
81
+ }
82
+ }
83
+ return findings;
84
+ }
85
+ function collectConfigCaptures(text, key, pattern, evidence, detector) {
86
+ const findings = [];
87
+ const re = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`);
88
+ let match;
89
+ while ((match = re.exec(text)) !== null) {
90
+ const captured = firstCapture(match);
91
+ if (!captured)
92
+ continue;
93
+ const start = captureOffset(match, captured.groupIndex);
94
+ pushFinding(findings, key, start, start + captured.value.length, evidence, detector);
95
+ }
96
+ return findings;
97
+ }
98
+ export function gitIdentityDetector(policy) {
99
+ const meta = { name: 'git-identity', version: VERSION };
100
+ return {
101
+ ...meta,
102
+ detect(attributes) {
103
+ const findings = [];
104
+ for (const [key, value] of Object.entries(attributes)) {
105
+ if (typeof value !== 'string' || classifyKey(key, policy) !== 'content')
106
+ continue;
107
+ findings.push(...collectTrailers(value, key, meta));
108
+ findings.push(...collectConfigCaptures(value, key, GIT_CONFIG_NAME, 'git-identity:user-name', meta));
109
+ findings.push(...collectConfigCaptures(value, key, GIT_CONFIG_EMAIL, 'git-identity:user-email', meta));
110
+ findings.push(...collectConfigCaptures(value, key, USER_NAME_ASSIGN, 'git-identity:user-name', meta));
111
+ findings.push(...collectConfigCaptures(value, key, USER_EMAIL_ASSIGN, 'git-identity:user-email', meta));
112
+ }
113
+ return findings;
114
+ },
115
+ };
116
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Vendored gitleaks rule pack — A1 (#1972 / design §6.2, Q6).
3
+ *
4
+ * Small pinned MIT subset. Refresh manually; no build-time sync.
5
+ * Regexes are JS-adapted (no Go inline flag verbs).
6
+ */
7
+ import { GITLEAKS_PACK } from './data/gitleaks-rules.js';
8
+ import { type KeyPolicy } from './key-policy.js';
9
+ import type { Detector } from './finding.js';
10
+ export type GitleaksRule = (typeof GITLEAKS_PACK.rules)[number];
11
+ export type GitleaksPack = typeof GITLEAKS_PACK;
12
+ export declare function loadGitleaksPack(): GitleaksPack;
13
+ export declare function gitleaksDetector(policy: KeyPolicy): Detector;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Vendored gitleaks rule pack — A1 (#1972 / design §6.2, Q6).
3
+ *
4
+ * Small pinned MIT subset. Refresh manually; no build-time sync.
5
+ * Regexes are JS-adapted (no Go inline flag verbs).
6
+ */
7
+ import { GITLEAKS_PACK } from './data/gitleaks-rules.js';
8
+ import { classifyKey } from './key-policy.js';
9
+ const VERSION = '1.0.0';
10
+ export function loadGitleaksPack() {
11
+ return GITLEAKS_PACK;
12
+ }
13
+ export function gitleaksDetector(policy) {
14
+ const meta = { name: 'gitleaks', version: VERSION };
15
+ const pack = loadGitleaksPack();
16
+ return {
17
+ ...meta,
18
+ detect(attributes) {
19
+ const findings = [];
20
+ for (const [key, value] of Object.entries(attributes)) {
21
+ if (typeof value !== 'string' || classifyKey(key, policy) !== 'content')
22
+ continue;
23
+ for (const rule of pack.rules) {
24
+ const re = new RegExp(rule.regex, 'gi');
25
+ let m;
26
+ while ((m = re.exec(value)) !== null) {
27
+ const full = m[0];
28
+ if (!full)
29
+ continue;
30
+ findings.push({
31
+ class: 'A1',
32
+ span: { key, start: m.index, end: m.index + full.length },
33
+ confidence: 'VERY_HIGH',
34
+ evidence: [`gitleaks:${rule.id}`],
35
+ detector: meta,
36
+ });
37
+ if (m[0].length === 0)
38
+ re.lastIndex += 1;
39
+ }
40
+ }
41
+ }
42
+ return findings;
43
+ },
44
+ };
45
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * GLiNER ONNX ML PII detector (#1973 / design §5.1, §6.3).
3
+ *
4
+ * Local-only inference behind the {@link PiiDetector} seam. Default model is
5
+ * `urchade/gliner_multi_pii-v1` (Apache-2.0, PII-tuned, multilingual). ONNX
6
+ * weights load from the community mirror `onnx-community/gliner_multi_pii-v1`
7
+ * (same weights; Transformers.js-compatible layout).
8
+ *
9
+ * Benchmark note (locked Q3): `urchade/gliner_multi_pii-v1` is the pinned
10
+ * default. The smaller `knowledgator/gliner-pii-edge-v1.0` family remains an
11
+ * alternate via `captures.piiDetection.model` — operators should measure
12
+ * Fβ=2 + latency on their local corpus before switching.
13
+ *
14
+ * Download-on-first-run is intentional (same posture as the retired
15
+ * bert-base-NER path). Init failure is fail-closed via
16
+ * {@link maybeBuildPiiDetector}.
17
+ */
18
+ import type { PiiDetector, PiiEntity } from './ml-pii-stage.js';
19
+ /**
20
+ * Logical model id operators configure. ONNX weights for the default pin live
21
+ * under the onnx-community mirror (see {@link resolveOnnxModelId}).
22
+ */
23
+ export declare const DEFAULT_GLINER_MODEL = "urchade/gliner_multi_pii-v1";
24
+ /** Alternate smaller pin — set via config `captures.piiDetection.model`. */
25
+ export declare const ALTERNATE_GLINER_EDGE_MODEL = "knowledgator/gliner-pii-edge-v1.0";
26
+ /**
27
+ * Zero-shot labels pinned in config (#1974 will hash these into the policy
28
+ * hash). Keep the list stable and reviewable — do not derive from model
29
+ * metadata at runtime.
30
+ */
31
+ export declare const DEFAULT_GLINER_PII_LABELS: readonly string[];
32
+ export interface GlinerPiiDetectorOptions {
33
+ /**
34
+ * Hugging Face model id. Default {@link DEFAULT_GLINER_MODEL}.
35
+ * Pass an onnx-community / knowledgator id to override the pin.
36
+ */
37
+ model?: string;
38
+ /** Zero-shot labels. Defaults to {@link DEFAULT_GLINER_PII_LABELS}. */
39
+ labels?: readonly string[];
40
+ /** Minimum score to emit (pre-band). Default 0.5. */
41
+ threshold?: number;
42
+ }
43
+ /** Map a logical / source model id onto an ONNX-capable Hugging Face repo. */
44
+ export declare function resolveOnnxModelId(modelId: string): string;
45
+ /**
46
+ * Production GLiNER PII detector. Loads ONNX weights via `@lmoe/gliner-onnx`
47
+ * (dynamic import so unit tests never pull the runtime). Call {@link init}
48
+ * before {@link detect}; {@link maybeBuildPiiDetector} owns that lifecycle.
49
+ */
50
+ export declare class GlinerPiiDetector implements PiiDetector {
51
+ private runtime;
52
+ private initPromise;
53
+ private readonly model;
54
+ private readonly labels;
55
+ private readonly threshold;
56
+ constructor(opts?: GlinerPiiDetectorOptions);
57
+ /** Logical model id (hashed into the policy digest — #1974). */
58
+ get modelId(): string;
59
+ /** Zero-shot labels (hashed into the policy digest — #1974). */
60
+ get labelSet(): readonly string[];
61
+ /** Eagerly load the model (daemon boot / first publish). */
62
+ init(): Promise<void>;
63
+ private ensureReady;
64
+ detect(text: string): Promise<PiiEntity[]>;
65
+ }