@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,74 @@
1
+ /**
2
+ * Apply policy dispositions to an attribute bag after one detection pass
3
+ * (#1969 / design §6.5).
4
+ */
5
+ import type { Finding } from './finding.js';
6
+ import { type PolicyTable } from './policy.js';
7
+ import { type ReviewQueueStore } from './review-queue.js';
8
+ import type { Attributes, RedactionRecord } from './types.js';
9
+ export { RejectPublishError, assertNoRejectPublish, rejectPublishFindings } from './reject-publish-error.js';
10
+ export interface ApplyDispositionsOptions {
11
+ policy?: PolicyTable;
12
+ /**
13
+ * Check-mode consumers (layer-2 distill, episode refuse): any non-pass
14
+ * disposition rejects. Still applies redacts so the body change is visible.
15
+ */
16
+ checkMode?: boolean;
17
+ /**
18
+ * Optional review-queue store. When provided, resolved flags are honored
19
+ * (`approve-instance` → pass, `redact-instance` → redact, allowlist /
20
+ * identity-pack → pass). Unresolved flags stay as `flag` and are listed in
21
+ * {@link ApplyDispositionsResult.unresolvedFlags}.
22
+ */
23
+ reviewStore?: ReviewQueueStore;
24
+ }
25
+ export interface ApplyDispositionsResult {
26
+ attributes: Attributes;
27
+ redactions: RedactionRecord[];
28
+ findings: Finding[];
29
+ /** True when a reject-publish disposition fired, or check-mode saw a non-pass. */
30
+ rejected: boolean;
31
+ /** Flag findings that remain unresolved after consulting the review queue. */
32
+ unresolvedFlags: Finding[];
33
+ /**
34
+ * Applied disposition counts keyed `${ScrubClass}:${redact|flag|reject}`
35
+ * (#1974). Pass dispositions are omitted.
36
+ */
37
+ perClassCounts: Record<string, number>;
38
+ }
39
+ /** Stub text for a redact disposition, keyed by class (+ optional evidence hint). */
40
+ export declare function stubForFinding(finding: Finding, occurrenceIndex: number): string;
41
+ /**
42
+ * Apply dispositions. Span redacts are applied right-to-left per key so earlier
43
+ * offsets stay valid. A5 reject-publish on a drop-key finding removes the key.
44
+ */
45
+ export declare function applyDispositions(attributes: Attributes, findings: Finding[], opts?: ApplyDispositionsOptions): ApplyDispositionsResult;
46
+ /**
47
+ * Allowlist-pass records are auditable Legibility receipts, not detections.
48
+ * They must not trip refuse-on-detection / distill check-mode gates.
49
+ */
50
+ export declare function countsTowardRefusal(record: RedactionRecord): boolean;
51
+ /** Redaction records that imply content was scrubbed or must refuse. */
52
+ export declare function refusalRedactions(redactions: RedactionRecord[]): RedactionRecord[];
53
+ /**
54
+ * True when publish/check should refuse the item.
55
+ *
56
+ * Prefer the explicit `rejected` bit (set by dispositions / check-mode).
57
+ * Fall back to refusal-relevant redaction records only — never `allowlist-pass`.
58
+ * For reject-publish classes (A4/A5 content), prefer
59
+ * {@link assertNoRejectPublish} so the abort is a loud class-named error.
60
+ * For unresolved flags, prefer {@link assertNoUnresolvedFlags} from
61
+ * `review-queue.ts`.
62
+ */
63
+ export declare function shouldRejectPublish(result: {
64
+ rejected?: boolean;
65
+ redactions: RedactionRecord[];
66
+ findings?: Finding[];
67
+ unresolvedFlags?: Finding[];
68
+ }): boolean;
69
+ /**
70
+ * Apply dispositions, then abort loudly when a reject-publish class fired.
71
+ * Use at publish altitude (capture/emit) — not for check-mode distill, which
72
+ * maps redactions to rejection reasons without throwing.
73
+ */
74
+ export declare function applyDispositionsOrReject(attributes: Attributes, findings: Finding[], opts?: ApplyDispositionsOptions): ApplyDispositionsResult;
@@ -0,0 +1,309 @@
1
+ /**
2
+ * Apply policy dispositions to an attribute bag after one detection pass
3
+ * (#1969 / design §6.5).
4
+ */
5
+ import { DEFAULT_POLICY, checkModeRejects, resolveDisposition, } from './policy.js';
6
+ import { incrementPerClassCount } from './provenance.js';
7
+ import { assertNoRejectPublish } from './reject-publish-error.js';
8
+ import { findingFingerprint, } from './review-queue.js';
9
+ export { RejectPublishError, assertNoRejectPublish, rejectPublishFindings } from './reject-publish-error.js';
10
+ /** Stub text for a redact disposition, keyed by class (+ optional evidence hint). */
11
+ export function stubForFinding(finding, occurrenceIndex) {
12
+ const hint = finding.evidence[0] ?? '';
13
+ switch (finding.class) {
14
+ case 'B1':
15
+ return '[EMAIL]';
16
+ case 'B2':
17
+ // Joint carrier: user.email values stub as email; names as NAME.
18
+ if (hint.includes('email'))
19
+ return '[EMAIL]';
20
+ return '[NAME]';
21
+ case 'D1':
22
+ return '/users/anon';
23
+ case 'C1':
24
+ return `[ETH_ADDR_${occurrenceIndex}]`;
25
+ case 'B3':
26
+ return '[NAME]';
27
+ case 'B4':
28
+ return '[USERNAME]';
29
+ case 'D2':
30
+ return '[IP]';
31
+ case 'D3':
32
+ return '[HOSTNAME]';
33
+ case 'A1':
34
+ if (hint.includes('aws-access-key-id'))
35
+ return '[SECRET:aws-access-key-id]';
36
+ if (hint.includes('gcp-api-key'))
37
+ return '[SECRET:gcp-api-key]';
38
+ if (hint.startsWith('secret:'))
39
+ return `[SECRET:${hint.slice('secret:'.length)}]`;
40
+ return '[SECRET:redacted]';
41
+ case 'A2':
42
+ return '[SECRET:high-entropy]';
43
+ case 'A3':
44
+ return '[SECRET:url-credential]';
45
+ case 'B7':
46
+ if (hint.includes('iban'))
47
+ return '[IBAN]';
48
+ return '[CARD]';
49
+ default: {
50
+ if (hint.startsWith('ml:')) {
51
+ return `[PII:${hint.slice('ml:'.length)}]`;
52
+ }
53
+ if (hint.startsWith('gitleaks:')) {
54
+ return `[SECRET:${hint.slice('gitleaks:'.length)}]`;
55
+ }
56
+ const label = hint || finding.class;
57
+ return `[PII:${label}]`;
58
+ }
59
+ }
60
+ }
61
+ function redactionKind(scrubClass, _finding) {
62
+ if (scrubClass.startsWith('A'))
63
+ return 'secret';
64
+ return 'pii';
65
+ }
66
+ function redactionDetail(finding) {
67
+ const hint = finding.evidence[0] ?? finding.class;
68
+ if (hint === 'drop-key' || hint === 'machine-identity-key')
69
+ return undefined;
70
+ // Normalize C1 to the shipped detail id so seed/trace/layer2 records match.
71
+ if (finding.class === 'C1')
72
+ return 'eth-address';
73
+ if (hint.startsWith('secret:'))
74
+ return hint.slice('secret:'.length);
75
+ if (hint.startsWith('gitleaks:'))
76
+ return hint.slice('gitleaks:'.length);
77
+ if (hint.startsWith('ml:'))
78
+ return hint.slice('ml:'.length);
79
+ return hint;
80
+ }
81
+ function isKeyDropFinding(finding) {
82
+ return (finding.evidence.includes('drop-key') ||
83
+ finding.evidence.includes('machine-identity-key'));
84
+ }
85
+ function decisionOverridesFlag(decision) {
86
+ if (decision === 'redact-instance')
87
+ return 'redact';
88
+ if (decision === 'approve-instance' ||
89
+ decision === 'add-to-allowlist' ||
90
+ decision === 'add-to-identity-pack') {
91
+ return 'pass';
92
+ }
93
+ return undefined;
94
+ }
95
+ function effectiveDisposition(finding, policy, checkMode, reviewStore) {
96
+ let disposition = resolveDisposition(finding.class, finding.confidence, policy);
97
+ if (disposition === 'flag' && reviewStore) {
98
+ const override = decisionOverridesFlag(reviewStore.resolutionFor(finding));
99
+ if (override)
100
+ disposition = override;
101
+ }
102
+ if (checkMode && disposition !== 'pass') {
103
+ // Check-mode still applies the underlying redact/reject action; the
104
+ // rejected bit is set separately via checkModeRejects.
105
+ return disposition;
106
+ }
107
+ return disposition;
108
+ }
109
+ /**
110
+ * Apply dispositions. Span redacts are applied right-to-left per key so earlier
111
+ * offsets stay valid. A5 reject-publish on a drop-key finding removes the key.
112
+ */
113
+ export function applyDispositions(attributes, findings, opts = {}) {
114
+ const policy = opts.policy ?? DEFAULT_POLICY;
115
+ const checkMode = opts.checkMode ?? false;
116
+ const reviewStore = opts.reviewStore;
117
+ const out = { ...attributes };
118
+ const redactions = [];
119
+ const unresolvedFlags = [];
120
+ const perClassCounts = {};
121
+ let rejected = false;
122
+ // Key-drop findings first (A5 structural drop-key, D3 machine-identity).
123
+ // Structural key drops remove the attribute; they do NOT reject-publish in
124
+ // redact-mode (content-level A4/A5 spans own the loud abort). Check-mode
125
+ // still rejects so distill stays fail-closed on any non-pass.
126
+ for (const finding of findings) {
127
+ if (!isKeyDropFinding(finding))
128
+ continue;
129
+ const disposition = effectiveDisposition(finding, policy, checkMode, reviewStore);
130
+ if (disposition === 'pass')
131
+ continue;
132
+ delete out[finding.span.key];
133
+ redactions.push({
134
+ key: finding.span.key,
135
+ stage: finding.detector.name,
136
+ kind: 'dropped-key',
137
+ });
138
+ // Structural drops are applied as redacts (key removed), even when the
139
+ // policy row for A5 content is reject-publish.
140
+ incrementPerClassCount(perClassCounts, finding.class, 'redact');
141
+ if (checkMode)
142
+ rejected = true;
143
+ }
144
+ // Group remaining span findings by key, apply right-to-left.
145
+ const byKey = new Map();
146
+ for (const finding of findings) {
147
+ if (isKeyDropFinding(finding))
148
+ continue;
149
+ if (!(finding.span.key in out))
150
+ continue;
151
+ const list = byKey.get(finding.span.key) ?? [];
152
+ list.push(finding);
153
+ byKey.set(finding.span.key, list);
154
+ }
155
+ for (const [key, keyFindings] of byKey) {
156
+ const value = out[key];
157
+ if (typeof value !== 'string')
158
+ continue;
159
+ const sorted = [...keyFindings].sort((a, b) => b.span.start - a.span.start);
160
+ const covered = [];
161
+ let text = value;
162
+ // Collect redact records left-to-right (unshift while applying right-to-left)
163
+ // so callers that pin redaction order keep a stable, start-ascending list.
164
+ const redactRecords = [];
165
+ for (const finding of sorted) {
166
+ const disposition = effectiveDisposition(finding, policy, checkMode, reviewStore);
167
+ if (disposition === 'pass') {
168
+ // Auditable allowlist hit (§6.4): record "we saw it and passed it on
169
+ // purpose". These must NOT count toward refuse-on-detection /
170
+ // check-mode rejection (kind `allowlist-pass` is filtered by
171
+ // {@link countsTowardRefusal}).
172
+ const allowEvidence = finding.evidence.find((e) => e.startsWith('allowlist:'));
173
+ if (allowEvidence) {
174
+ redactions.push({
175
+ key,
176
+ stage: finding.detector.name,
177
+ kind: 'allowlist-pass',
178
+ detail: allowEvidence.slice('allowlist:'.length),
179
+ });
180
+ }
181
+ continue;
182
+ }
183
+ if (disposition === 'reject-publish') {
184
+ // Always stub the span so A4/A5 plaintext never survives into
185
+ // check-mode / in-memory capture envelopes; abort is still signaled
186
+ // via `rejected` + assertNoRejectPublish at publish altitude.
187
+ rejected = true;
188
+ const { start, end } = finding.span;
189
+ if (start >= 0 && end <= text.length && start < end &&
190
+ !covered.some((c) => start < c.end && end > c.start)) {
191
+ const leftIndex = keyFindings.filter((f) => {
192
+ const d = effectiveDisposition(f, policy, checkMode, reviewStore);
193
+ return (d === 'redact' || d === 'reject-publish') && f.span.start < start;
194
+ }).length;
195
+ const stub = stubForFinding(finding, leftIndex);
196
+ text = text.slice(0, start) + stub + text.slice(end);
197
+ covered.push({ start, end });
198
+ }
199
+ redactRecords.unshift({
200
+ key,
201
+ stage: finding.detector.name,
202
+ kind: redactionKind(finding.class, finding),
203
+ detail: redactionDetail(finding),
204
+ });
205
+ incrementPerClassCount(perClassCounts, finding.class, disposition);
206
+ continue;
207
+ }
208
+ if (disposition === 'flag') {
209
+ unresolvedFlags.push(finding);
210
+ incrementPerClassCount(perClassCounts, finding.class, disposition);
211
+ // In redact-mode leave text (review queue owns the hold). In check-mode,
212
+ // record so consumers reject.
213
+ if (checkMode) {
214
+ rejected = true;
215
+ redactions.push({
216
+ key,
217
+ stage: finding.detector.name,
218
+ kind: 'flag',
219
+ detail: redactionDetail(finding),
220
+ });
221
+ }
222
+ continue;
223
+ }
224
+ // redact
225
+ const { start, end } = finding.span;
226
+ if (start < 0 || end > text.length || start >= end)
227
+ continue;
228
+ if (covered.some((c) => start < c.end && end > c.start))
229
+ continue;
230
+ const leftIndex = keyFindings.filter((f) => {
231
+ if (effectiveDisposition(f, policy, checkMode, reviewStore) !== 'redact')
232
+ return false;
233
+ return f.span.start < start;
234
+ }).length;
235
+ const stub = stubForFinding(finding, leftIndex);
236
+ text = text.slice(0, start) + stub + text.slice(end);
237
+ covered.push({ start, end });
238
+ redactRecords.unshift({
239
+ key,
240
+ stage: finding.detector.name,
241
+ kind: redactionKind(finding.class, finding),
242
+ detail: redactionDetail(finding),
243
+ });
244
+ incrementPerClassCount(perClassCounts, finding.class, disposition);
245
+ if (checkMode)
246
+ rejected = true;
247
+ }
248
+ redactions.push(...redactRecords);
249
+ out[key] = text;
250
+ }
251
+ if (checkMode && checkModeRejects(findings, policy)) {
252
+ rejected = true;
253
+ }
254
+ // Dedupe unresolved flags by fingerprint (nested walks can re-emit).
255
+ const seen = new Set();
256
+ const uniqueUnresolved = unresolvedFlags.filter((f) => {
257
+ const fp = findingFingerprint(f);
258
+ if (seen.has(fp))
259
+ return false;
260
+ seen.add(fp);
261
+ return true;
262
+ });
263
+ return {
264
+ attributes: out,
265
+ redactions,
266
+ findings,
267
+ rejected,
268
+ unresolvedFlags: uniqueUnresolved,
269
+ perClassCounts,
270
+ };
271
+ }
272
+ /**
273
+ * Allowlist-pass records are auditable Legibility receipts, not detections.
274
+ * They must not trip refuse-on-detection / distill check-mode gates.
275
+ */
276
+ export function countsTowardRefusal(record) {
277
+ return record.kind !== 'allowlist-pass';
278
+ }
279
+ /** Redaction records that imply content was scrubbed or must refuse. */
280
+ export function refusalRedactions(redactions) {
281
+ return redactions.filter(countsTowardRefusal);
282
+ }
283
+ /**
284
+ * True when publish/check should refuse the item.
285
+ *
286
+ * Prefer the explicit `rejected` bit (set by dispositions / check-mode).
287
+ * Fall back to refusal-relevant redaction records only — never `allowlist-pass`.
288
+ * For reject-publish classes (A4/A5 content), prefer
289
+ * {@link assertNoRejectPublish} so the abort is a loud class-named error.
290
+ * For unresolved flags, prefer {@link assertNoUnresolvedFlags} from
291
+ * `review-queue.ts`.
292
+ */
293
+ export function shouldRejectPublish(result) {
294
+ if (result.rejected)
295
+ return true;
296
+ if (result.unresolvedFlags && result.unresolvedFlags.length > 0)
297
+ return true;
298
+ return refusalRedactions(result.redactions).length > 0;
299
+ }
300
+ /**
301
+ * Apply dispositions, then abort loudly when a reject-publish class fired.
302
+ * Use at publish altitude (capture/emit) — not for check-mode distill, which
303
+ * maps redactions to rejection reasons without throwing.
304
+ */
305
+ export function applyDispositionsOrReject(attributes, findings, opts = {}) {
306
+ const applied = applyDispositions(attributes, findings, opts);
307
+ assertNoRejectPublish(applied, opts.policy ?? DEFAULT_POLICY);
308
+ return applied;
309
+ }
@@ -0,0 +1,75 @@
1
+ import { ScrubPipeline } from './pipeline.js';
2
+ import { type KeyPolicy } from './key-policy.js';
3
+ import { type PiiDetector } from './ml-pii-stage.js';
4
+ import { type AssembleKnownIdentityOptions, type AssembledKnownIdentity } from './known-identity-detector.js';
5
+ import type { Detector } from './finding.js';
6
+ /**
7
+ * Default key policy. `jinn.*` identity/chain attributes are structural and pass
8
+ * raw. The `drop` tier deletes the spec's stage-1 high-confidence keys outright
9
+ * (auth headers, cookies, env dumps — see `spec/2026-06-15-ts-trajectory-scrub-stack.md`):
10
+ * these never carry sellable content and are never safe to publish. Globs use the
11
+ * trailing-`*` prefix form `classifyKey` supports; the HTTP header keys are listed
12
+ * per request/response direction (a leading `*.header.…` glob is not matched, so
13
+ * we enumerate the concrete keys). Machine-identity keys (D3 carrier) drop
14
+ * attempt-manifest `host` / hostname telemetry. Everything else is `content` and
15
+ * flows through the value-scrubbing detectors.
16
+ */
17
+ export declare const DEFAULT_KEY_POLICY: KeyPolicy;
18
+ export interface BuildScrubPipelineOptions {
19
+ policy?: KeyPolicy;
20
+ /** When provided, the ML PII (GLiNER) detector is appended. */
21
+ piiDetector?: PiiDetector;
22
+ /** Known-identity pack + non-address allowlist (#1971). */
23
+ knownIdentity?: AssembleKnownIdentityOptions | AssembledKnownIdentity;
24
+ /** Review-queue store for flag resolutions (#1973). */
25
+ reviewStore?: import('./review-queue.js').ReviewQueueStore;
26
+ /**
27
+ * When false, unresolved flags do not abort (tests). Default true for
28
+ * redact-mode publish lanes.
29
+ */
30
+ failClosedOnUnresolvedFlags?: boolean;
31
+ }
32
+ export declare function resolveKnownIdentity(opts?: AssembleKnownIdentityOptions | AssembledKnownIdentity): AssembledKnownIdentity;
33
+ /** Build provenance extras from known-identity + optional ML detector (#1974). */
34
+ export declare function buildProvenanceExtras(knownIdentity: AssembledKnownIdentity, piiDetector?: PiiDetector): {
35
+ modelId: string | null;
36
+ labels: readonly string[];
37
+ allowlistDigest: string;
38
+ };
39
+ /**
40
+ * Shared detector inventory (#1969 / #1970 / #1971 / #1972 / #1973). Every
41
+ * publish/check consumer runs the same owned detectors (key-policy,
42
+ * plain-patterns including C1 wallet + A1 credential IDs, git-identity B2
43
+ * carriers, known-identity pack + non-address allowlist, Tier-1
44
+ * reject/URL/IP/instruments/gitleaks, secretlint). What varies is disposition /
45
+ * check-mode and whether an ML PII detector is injected. Seed keeps the entropy
46
+ * fallback off until A2 mid-band flags are absorbed without refuse-on-detection
47
+ * (#1409 shipped behavior). openredaction was retired in #1973.
48
+ */
49
+ export declare function sharedDetectorInventory(policy: KeyPolicy, opts?: {
50
+ entropyFallback?: boolean;
51
+ piiDetector?: PiiDetector;
52
+ knownIdentity?: AssembleKnownIdentityOptions | AssembledKnownIdentity;
53
+ }): Detector[];
54
+ /**
55
+ * Trace / redact-mode preset over the shared inventory.
56
+ *
57
+ * @deprecated Prefer thinking in terms of one inventory + policy; this builder
58
+ * remains as a compatibility preset through the migration (#1969).
59
+ */
60
+ export declare function buildScrubPipeline(opts?: BuildScrubPipelineOptions): ScrubPipeline;
61
+ export interface BuildSeedScrubPipelineOptions {
62
+ policy?: KeyPolicy;
63
+ knownIdentity?: AssembleKnownIdentityOptions | AssembledKnownIdentity;
64
+ piiDetector?: PiiDetector;
65
+ reviewStore?: import('./review-queue.js').ReviewQueueStore;
66
+ failClosedOnUnresolvedFlags?: boolean;
67
+ }
68
+ /**
69
+ * Seed / redact-mode preset (#1409 / #1969). Same owned inventory as layer-2.
70
+ * Entropy fallback stays off to preserve the shipped zero-corruption seed
71
+ * behavior until A2 mid-band can flag without tripping refuse-on-detection.
72
+ *
73
+ * @deprecated Compatibility preset over the one inventory + policy table.
74
+ */
75
+ export declare function buildSeedScrubPipeline(policyOrOpts?: KeyPolicy | BuildSeedScrubPipelineOptions): ScrubPipeline;
@@ -0,0 +1,143 @@
1
+ import { ScrubPipeline } from './pipeline.js';
2
+ import { keyPolicyDetector } from './key-policy.js';
3
+ import { plainPatternsDetector } from './plain-patterns-stage.js';
4
+ import { gitIdentityDetector } from './git-identity-detector.js';
5
+ import { secretlintDetector } from './secretlint-stage.js';
6
+ import { mlPiiDetector } from './ml-pii-stage.js';
7
+ import { assembleKnownIdentity, knownIdentityDetector, } from './known-identity-detector.js';
8
+ import { urlCredentialsDetector } from './url-credentials-detector.js';
9
+ import { rejectClassesDetector } from './reject-classes-detector.js';
10
+ import { checksummedInstrumentsDetector } from './checksummed-instruments-detector.js';
11
+ import { ipAddressDetector } from './ip-address-detector.js';
12
+ import { gitleaksDetector } from './gitleaks-detector.js';
13
+ import { DEFAULT_POLICY } from './policy.js';
14
+ import { computeAllowlistDigest } from './provenance.js';
15
+ /**
16
+ * Default key policy. `jinn.*` identity/chain attributes are structural and pass
17
+ * raw. The `drop` tier deletes the spec's stage-1 high-confidence keys outright
18
+ * (auth headers, cookies, env dumps — see `spec/2026-06-15-ts-trajectory-scrub-stack.md`):
19
+ * these never carry sellable content and are never safe to publish. Globs use the
20
+ * trailing-`*` prefix form `classifyKey` supports; the HTTP header keys are listed
21
+ * per request/response direction (a leading `*.header.…` glob is not matched, so
22
+ * we enumerate the concrete keys). Machine-identity keys (D3 carrier) drop
23
+ * attempt-manifest `host` / hostname telemetry. Everything else is `content` and
24
+ * flows through the value-scrubbing detectors.
25
+ */
26
+ export const DEFAULT_KEY_POLICY = {
27
+ safe: ['jinn.*'],
28
+ drop: [
29
+ 'http.request.header.authorization',
30
+ 'http.response.header.authorization',
31
+ 'http.request.header.cookie',
32
+ 'http.response.header.cookie',
33
+ 'http.request.header.set-cookie',
34
+ 'http.response.header.set-cookie',
35
+ 'env.*',
36
+ ],
37
+ machineIdentity: [
38
+ 'host',
39
+ 'hostname',
40
+ 'os.hostname',
41
+ 'attempt.host',
42
+ 'attempt.hostname',
43
+ 'system.hostname',
44
+ 'device.hostname',
45
+ 'device.host',
46
+ ],
47
+ };
48
+ export function resolveKnownIdentity(opts) {
49
+ // AssembledKnownIdentity is a subset of AssembleKnownIdentityOptions;
50
+ // re-assembly is idempotent (allowlist entries are deduped).
51
+ return assembleKnownIdentity(opts);
52
+ }
53
+ /** Duck-type ML detector metadata for the policy hash (#1974). */
54
+ function provenanceFromPiiDetector(detector) {
55
+ if (!detector)
56
+ return { modelId: null, labels: [] };
57
+ const withMeta = detector;
58
+ if (typeof withMeta.modelId === 'string' && withMeta.labelSet) {
59
+ return { modelId: withMeta.modelId, labels: withMeta.labelSet };
60
+ }
61
+ return { modelId: null, labels: [] };
62
+ }
63
+ /** Build provenance extras from known-identity + optional ML detector (#1974). */
64
+ export function buildProvenanceExtras(knownIdentity, piiDetector) {
65
+ const ml = provenanceFromPiiDetector(piiDetector);
66
+ return {
67
+ modelId: ml.modelId,
68
+ labels: ml.labels,
69
+ allowlistDigest: computeAllowlistDigest(knownIdentity.allowlist.entries),
70
+ };
71
+ }
72
+ /**
73
+ * Shared detector inventory (#1969 / #1970 / #1971 / #1972 / #1973). Every
74
+ * publish/check consumer runs the same owned detectors (key-policy,
75
+ * plain-patterns including C1 wallet + A1 credential IDs, git-identity B2
76
+ * carriers, known-identity pack + non-address allowlist, Tier-1
77
+ * reject/URL/IP/instruments/gitleaks, secretlint). What varies is disposition /
78
+ * check-mode and whether an ML PII detector is injected. Seed keeps the entropy
79
+ * fallback off until A2 mid-band flags are absorbed without refuse-on-detection
80
+ * (#1409 shipped behavior). openredaction was retired in #1973.
81
+ */
82
+ export function sharedDetectorInventory(policy, opts = {}) {
83
+ const detectors = [keyPolicyDetector(policy)];
84
+ detectors.push(plainPatternsDetector(policy));
85
+ detectors.push(gitIdentityDetector(policy));
86
+ detectors.push(knownIdentityDetector(policy, { assembled: resolveKnownIdentity(opts.knownIdentity) }));
87
+ detectors.push(urlCredentialsDetector(policy));
88
+ detectors.push(rejectClassesDetector(policy));
89
+ detectors.push(checksummedInstrumentsDetector(policy));
90
+ detectors.push(ipAddressDetector(policy));
91
+ detectors.push(gitleaksDetector(policy));
92
+ detectors.push(secretlintDetector(policy, { entropyFallback: opts.entropyFallback ?? true }));
93
+ if (opts.piiDetector) {
94
+ detectors.push(mlPiiDetector(policy, opts.piiDetector));
95
+ }
96
+ return detectors;
97
+ }
98
+ /**
99
+ * Trace / redact-mode preset over the shared inventory.
100
+ *
101
+ * @deprecated Prefer thinking in terms of one inventory + policy; this builder
102
+ * remains as a compatibility preset through the migration (#1969).
103
+ */
104
+ export function buildScrubPipeline(opts = {}) {
105
+ const policy = opts.policy ?? DEFAULT_KEY_POLICY;
106
+ const knownIdentity = resolveKnownIdentity(opts.knownIdentity);
107
+ return new ScrubPipeline(sharedDetectorInventory(policy, {
108
+ entropyFallback: true,
109
+ piiDetector: opts.piiDetector,
110
+ knownIdentity,
111
+ }), {
112
+ policy: DEFAULT_POLICY,
113
+ checkMode: false,
114
+ reviewStore: opts.reviewStore,
115
+ failClosedOnUnresolvedFlags: opts.failClosedOnUnresolvedFlags,
116
+ provenance: buildProvenanceExtras(knownIdentity, opts.piiDetector),
117
+ });
118
+ }
119
+ /**
120
+ * Seed / redact-mode preset (#1409 / #1969). Same owned inventory as layer-2.
121
+ * Entropy fallback stays off to preserve the shipped zero-corruption seed
122
+ * behavior until A2 mid-band can flag without tripping refuse-on-detection.
123
+ *
124
+ * @deprecated Compatibility preset over the one inventory + policy table.
125
+ */
126
+ export function buildSeedScrubPipeline(policyOrOpts = DEFAULT_KEY_POLICY) {
127
+ const opts = policyOrOpts && 'safe' in policyOrOpts
128
+ ? { policy: policyOrOpts }
129
+ : policyOrOpts;
130
+ const policy = opts.policy ?? DEFAULT_KEY_POLICY;
131
+ const knownIdentity = resolveKnownIdentity(opts.knownIdentity);
132
+ return new ScrubPipeline(sharedDetectorInventory(policy, {
133
+ entropyFallback: false,
134
+ piiDetector: opts.piiDetector,
135
+ knownIdentity,
136
+ }), {
137
+ policy: DEFAULT_POLICY,
138
+ checkMode: false,
139
+ reviewStore: opts.reviewStore,
140
+ failClosedOnUnresolvedFlags: opts.failClosedOnUnresolvedFlags,
141
+ provenance: buildProvenanceExtras(knownIdentity, opts.piiDetector),
142
+ });
143
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * B7 — checksummed payment instruments (#1972 / design §6.2).
3
+ *
4
+ * Card numbers (Luhn) and IBANs (mod-97). SSN-shape stays context-gated and is
5
+ * not emitted here.
6
+ */
7
+ import { type KeyPolicy } from './key-policy.js';
8
+ import type { Detector } from './finding.js';
9
+ export declare function checksummedInstrumentsDetector(policy: KeyPolicy): Detector;
10
+ /** ISO/IEC 7812 Luhn checksum. */
11
+ export declare function luhnOk(digits: string): boolean;
12
+ /** ISO 13616 IBAN mod-97 check. */
13
+ export declare function ibanMod97Ok(iban: string): boolean;