sfscanner 0.1.0 → 0.2.0

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.
@@ -1,11 +1,12 @@
1
- import { type RuleCatalogEntry } from '@hub/scanner-core/rule-catalog';
2
- import type { CliCommandV2 } from './arguments';
1
+ import type { CliCommandV2 } from './arguments.js';
2
+ type CliSeverityV2 = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW' | 'INFO';
3
+ type CliConfidenceV2 = 'LOW' | 'MEDIUM' | 'HIGH';
3
4
  export type CliRuleSummaryV2 = Readonly<{
4
5
  ruleId: string;
5
6
  revision: string;
6
7
  effect: 'SCORING' | 'REPORT_ONLY';
7
- severity: RuleCatalogEntry['defaultSeverity'];
8
- confidence: RuleCatalogEntry['defaultConfidence'];
8
+ severity: CliSeverityV2;
9
+ confidence: CliConfidenceV2;
9
10
  capabilityId: string;
10
11
  evidenceSchemaId: string;
11
12
  remediationId: string;
@@ -13,4 +13,4 @@ export type ConfigLoadResult = Readonly<{
13
13
  }>;
14
14
  export declare function loadExplicitConfig(input: unknown, options?: ConfigLoadOptions): ConfigLoadResult;
15
15
  export declare function validateScanConfigV2(input: unknown): ConfigLoadResult;
16
- export { ADMITTED_PROFILE_V2, BUILTIN_AUTHORITIES_V2, BUILTIN_LIMITS_AUTHORITY_V2, BUILTIN_RULESET_AUTHORITY_V2, BUILTIN_SEMANTICS_AUTHORITY_V2, DEFAULT_SCAN_CONFIG_V2, PROFILE_MANIFEST_DIGEST_V2, RULE_PACK_DIGEST_V2, SCAN_CONFIG_SCHEMA_V2, SEMANTIC_CATALOGUE_DIGEST_V2, type ScanConfigV2, } from './contracts';
16
+ export { ADMITTED_PROFILE_V2, BUILTIN_AUTHORITIES_V2, BUILTIN_LIMITS_AUTHORITY_V2, BUILTIN_RULESET_AUTHORITY_V2, BUILTIN_SEMANTICS_AUTHORITY_V2, CLI_TOOL_V2, DEFAULT_SCAN_CONFIG_V2, PROFILE_MANIFEST_DIGEST_V2, RULE_PACK_DIGEST_V2, SCAN_CONFIG_SCHEMA_V2, SEMANTIC_CATALOGUE_DIGEST_V2, type ScanConfigV2, } from './contracts';
@@ -17,6 +17,7 @@ export type ScanConfigV2 = Readonly<{
17
17
  target?: string;
18
18
  include?: readonly string[];
19
19
  exclude?: readonly string[];
20
+ approvedDomains?: readonly string[];
20
21
  allowUnsignedDevelopmentRules?: boolean;
21
22
  noColor?: boolean;
22
23
  }>;
@@ -47,8 +48,8 @@ export declare const BUILTIN_AUTHORITIES_V2: Readonly<{
47
48
  }>;
48
49
  semantics: Readonly<{
49
50
  id: "scanner-core-builtins";
50
- version: "2.0.0";
51
- digest: "1bb11f63269d427cb28e141dfe0a6a1512e53c298abdb4d6450545ceb56d11c4";
51
+ version: "2.2.0";
52
+ digest: "bc6fdecc34e460b5b01973ef7a8ce06b27b9b100ae03b480110f11f2a63ac328";
52
53
  }>;
53
54
  limits: Readonly<{
54
55
  schemaVersion: "scanner-limits-v2";
@@ -64,17 +65,33 @@ export declare const BUILTIN_RULESET_AUTHORITY_V2: Readonly<{
64
65
  }>;
65
66
  export declare const BUILTIN_SEMANTICS_AUTHORITY_V2: Readonly<{
66
67
  id: "scanner-core-builtins";
67
- version: "2.0.0";
68
- digest: "1bb11f63269d427cb28e141dfe0a6a1512e53c298abdb4d6450545ceb56d11c4";
68
+ version: "2.2.0";
69
+ digest: "bc6fdecc34e460b5b01973ef7a8ce06b27b9b100ae03b480110f11f2a63ac328";
69
70
  }>;
70
71
  export declare const BUILTIN_LIMITS_AUTHORITY_V2: Readonly<{
71
72
  schemaVersion: "scanner-limits-v2";
72
73
  digest: "fca787753ca88f71cf0d174427f4ac730a7aef1c823aaf73aa1ea72af92b4077";
73
74
  }>;
74
75
  export declare const RULE_PACK_DIGEST_V2: "a36d00564bbf8e8c38f05820480782cdc53fdbd3712558850066c4e3f5c3413d";
75
- export declare const SEMANTIC_CATALOGUE_DIGEST_V2: "1bb11f63269d427cb28e141dfe0a6a1512e53c298abdb4d6450545ceb56d11c4";
76
+ export declare const SEMANTIC_CATALOGUE_DIGEST_V2: "bc6fdecc34e460b5b01973ef7a8ce06b27b9b100ae03b480110f11f2a63ac328";
77
+ /**
78
+ * One tool identity for every machine surface: the JSON/NDJSON scan envelopes and the
79
+ * rules/explain/profiles metadata documents. The digest folds the admitted engine, profile,
80
+ * ruleset, and semantics authorities, so a re-pin moves every surface together instead of
81
+ * leaving a frozen literal behind on one of them.
82
+ */
83
+ export declare const CLI_VERSION_V2: "0.1.0";
84
+ export type CliToolIdentityV2 = Readonly<{
85
+ name: 'n8n-scan';
86
+ version: string;
87
+ buildDigest: string;
88
+ }>;
89
+ export declare const CLI_TOOL_V2: CliToolIdentityV2;
76
90
  export declare const DEFAULT_SCAN_CONFIG_V2: ScanConfigV2;
77
- export declare const SCAN_CONFIG_V2_FIELDS: readonly ["schemaVersion", "format", "profile", "output", "inputs", "configPath", "failOn", "requireComplete", "concurrency", "rules", "catalogue", "limits", "target", "include", "exclude", "allowUnsignedDevelopmentRules", "noColor"];
91
+ export declare const SCAN_CONFIG_V2_FIELDS: readonly ["schemaVersion", "format", "profile", "output", "inputs", "configPath", "failOn", "requireComplete", "concurrency", "rules", "catalogue", "limits", "target", "include", "exclude", "approvedDomains", "allowUnsignedDevelopmentRules", "noColor"];
78
92
  export declare const SCAN_CONFIG_V2_MAX_STRING = 4096;
79
93
  export declare const SCAN_CONFIG_V2_MAX_PATTERNS = 1024;
80
94
  export declare const SCAN_CONFIG_V2_MAX_CONCURRENCY = 16;
95
+ export declare const SCAN_CONFIG_V2_MAX_DOMAINS = 1000;
96
+ export declare const DOMAIN_V2: RegExp;
97
+ export declare function isApprovedDomainV2(value: unknown): value is string;
package/dist/v2/help.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export declare const V2_COMMANDS: readonly ["scan", "rules", "explain", "profiles", "validate-config", "version"];
2
2
  export type V2CommandName = (typeof V2_COMMANDS)[number];
3
- export type V2OptionName = '--help' | '--schema' | '--format' | '--profile' | '--fail-on' | '--require-complete' | '--max-concurrency' | '--concurrency' | '--timeout' | '--output' | '--config' | '--rules' | '--catalogue' | '--limits' | '--target' | '--n8n-version' | '--edition' | '--deployment' | '--role' | '--task-runner' | '--community-package' | '--configuration' | '--include' | '--exclude' | '--allow-unsigned-development-rules' | '--no-color';
3
+ export type V2OptionName = '--help' | '--schema' | '--format' | '--profile' | '--fail-on' | '--require-complete' | '--max-concurrency' | '--concurrency' | '--timeout' | '--output' | '--config' | '--rules' | '--catalogue' | '--limits' | '--target' | '--n8n-version' | '--edition' | '--deployment' | '--role' | '--task-runner' | '--community-package' | '--configuration' | '--approved-domain' | '--include' | '--exclude' | '--allow-unsigned-development-rules' | '--no-color';
4
4
  export type V2HelpOption = Readonly<{
5
5
  name: V2OptionName;
6
6
  value: string;
@@ -9,7 +9,13 @@ export type V2HelpOption = Readonly<{
9
9
  default?: string;
10
10
  }>;
11
11
  export declare const V2_OPTIONS: readonly V2HelpOption[];
12
- export type V2LimitName = 'maxBytes' | 'maxNodes' | 'maxEdges' | 'maxConnectionDepth' | 'maxParameterDepth' | 'maxParameterLeaves' | 'workerDeadlineMs' | 'maxSyntaxNodes' | 'maxDocumentDepth' | 'maxMembersAndElements' | 'maxRawEdges' | 'maxDuplicateKeys' | 'maxDiagnostics' | 'maxStoredSourceSpans' | 'maxExpressionTokens' | 'maxCodeSyntaxNodes' | 'maxSourceWork' | 'maxGraphOperations' | 'maxTaintStates' | 'maxTraceStepsPerFinding' | 'maxFindings' | 'maxRedactionPatches' | 'maxRedactedArtifactBytes' | 'maxEvidenceBytes' | 'maxSerializedResultBytes' | 'maxRuleOperations';
12
+ /**
13
+ * The published limit vocabulary, restated because a shipped declaration must not name the private,
14
+ * unpublished contracts workspace: a strict NodeNext consumer of the packed package fails `TS2307`
15
+ * on that import. The values below still come from the contract, and the two vocabularies stay
16
+ * exhaustive against each other at compile time.
17
+ */
18
+ export type V2LimitName = 'maxBytes' | 'maxNodes' | 'maxEdges' | 'maxConnectionDepth' | 'maxParameterDepth' | 'maxParameterLeaves' | 'workerDeadlineMs' | 'maxSyntaxNodes' | 'maxDocumentDepth' | 'maxMembersAndElements' | 'maxRawEdges' | 'maxDuplicateKeys' | 'maxDiagnostics' | 'maxStoredSourceSpans' | 'maxSourceWork' | 'maxExpressionTokens' | 'maxCodeSyntaxNodes' | 'maxGraphOperations' | 'maxTaintStates' | 'maxTraceStepsPerFinding' | 'maxFindings' | 'maxRedactionPatches' | 'maxRedactedArtifactBytes' | 'maxEvidenceBytes' | 'maxSerializedResultBytes' | 'maxRuleOperations';
13
19
  export declare const V2_LIMITS: Readonly<Record<V2LimitName, number>>;
14
20
  export declare const V2_EXIT_CODES: readonly [0, 1, 2, 3, 4, 130];
15
21
  export type V2ExitCode = (typeof V2_EXIT_CODES)[number];
package/dist/v2/help.js CHANGED
@@ -117,6 +117,49 @@ var DEFAULT_SCANNER_LIMITS_V2 = Object.freeze({
117
117
  maxSerializedResultBytes: 5e6,
118
118
  maxRuleOperations: 1e6
119
119
  });
120
+ var ADMITTED_LIMIT_STATES = Object.freeze({
121
+ maxBytes: "WIRED",
122
+ maxNodes: "WIRED",
123
+ maxEdges: "WIRED",
124
+ maxConnectionDepth: "WIRED",
125
+ maxParameterDepth: "WIRED",
126
+ maxParameterLeaves: "WIRED",
127
+ workerDeadlineMs: "WIRED",
128
+ maxSyntaxNodes: "WIRED",
129
+ maxDocumentDepth: "WIRED",
130
+ maxMembersAndElements: "WIRED",
131
+ maxRawEdges: "WIRED",
132
+ maxDuplicateKeys: "WIRED",
133
+ maxDiagnostics: "WIRED",
134
+ maxStoredSourceSpans: "WIRED",
135
+ maxSourceWork: "WIRED",
136
+ maxExpressionTokens: "DORMANT",
137
+ maxCodeSyntaxNodes: "DORMANT",
138
+ maxGraphOperations: "DORMANT",
139
+ maxTaintStates: "DORMANT",
140
+ maxTraceStepsPerFinding: "DORMANT",
141
+ maxFindings: "WIRED",
142
+ maxRedactionPatches: "UNWIRED",
143
+ maxRedactedArtifactBytes: "UNWIRED",
144
+ maxEvidenceBytes: "WIRED",
145
+ maxSerializedResultBytes: "WIRED",
146
+ maxRuleOperations: "WIRED"
147
+ });
148
+ var MEASURED_LIMIT_IDS = [
149
+ "maxBytes",
150
+ "maxNodes",
151
+ "maxEdges",
152
+ "maxConnectionDepth",
153
+ "maxParameterDepth",
154
+ "maxParameterLeaves",
155
+ "maxSyntaxNodes",
156
+ "maxRawEdges",
157
+ "maxDuplicateKeys",
158
+ "maxDiagnostics",
159
+ "maxStoredSourceSpans",
160
+ "maxSourceWork",
161
+ "maxFindings"
162
+ ];
120
163
 
121
164
  // src/v2/help.ts
122
165
  var V2_COMMANDS = Object.freeze([
@@ -152,6 +195,7 @@ var V2_OPTIONS = Object.freeze([
152
195
  { name: "--task-runner", value: "<LEGACY|NATIVE|UNKNOWN>", commands: SCAN, description: "Set the task-runner fact.", default: "UNKNOWN" },
153
196
  { name: "--community-package", value: "<package>", commands: SCAN, description: "Record a community package in target context; repeatable." },
154
197
  { name: "--configuration", value: "<name>", commands: SCAN, description: "Record a configuration fact; repeatable." },
198
+ { name: "--approved-domain", value: "<host>", commands: SCAN, description: "Treat this hostname as approved for UNAPPROVED_DOMAIN; repeatable." },
155
199
  { name: "--include", value: "<posix-glob>", commands: SCAN, description: "Replace config includes with a case-sensitive POSIX glob; repeatable.", default: "**/*.json" },
156
200
  { name: "--exclude", value: "<posix-glob>", commands: SCAN, description: "Replace config excludes with a case-sensitive POSIX glob; repeatable.", default: ".git/**, node_modules/**" },
157
201
  { name: "--allow-unsigned-development-rules", value: "", commands: SCAN, description: "Refused: external rule packs are not admitted, so no pack can be selected.", default: "false" },
@@ -185,10 +229,13 @@ var V2_LIMITS = Object.freeze({
185
229
  maxSerializedResultBytes: DEFAULT_SCANNER_LIMITS_V2.maxSerializedResultBytes,
186
230
  maxRuleOperations: DEFAULT_SCANNER_LIMITS_V2.maxRuleOperations
187
231
  });
188
- var UNWIRED_LIMITS = Object.freeze({
189
- maxRedactionPatches: true,
190
- maxRedactedArtifactBytes: true
191
- });
232
+ function limitStateMarker(name) {
233
+ const state = ADMITTED_LIMIT_STATES[name];
234
+ if (state === "WIRED") {
235
+ return MEASURED_LIMIT_IDS.some((id) => id === name) ? " (enforced; usage row)" : " (enforced; no usage row)";
236
+ }
237
+ return state === "DORMANT" ? " (dormant in beta-default; hardened-only)" : " (development-only, unwired)";
238
+ }
192
239
  var V2_EXIT_CODES = Object.freeze([0, 1, 2, 3, 4, 130]);
193
240
  var V2_EXIT_REASONS = Object.freeze([
194
241
  "CANCELLED",
@@ -249,7 +296,7 @@ var generalLines = [
249
296
  " beta-hardened@2.0.0: defined but not admitted or selectable.",
250
297
  "",
251
298
  "Limits:",
252
- ...Object.entries(V2_LIMITS).map(([name, value]) => ` ${name}=${value}${UNWIRED_LIMITS[name] ? " (development-only, unwired)" : ""}`),
299
+ ...Object.entries(V2_LIMITS).map(([name, value]) => ` ${name}=${value}${limitStateMarker(name)}`),
253
300
  "",
254
301
  "Exit codes and precedence:",
255
302
  " 130 CANCELLED; 4 INTERNAL; 2 USAGE; 1 FINDING_THRESHOLD or REQUIRE_COMPLETE; 3 ITEM_FAILURE; 0 UNDER_THRESHOLD.",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../contracts/exclusion-registry.ts", "../../../../contracts/limits.ts", "../../../../contracts/scanner-limits.ts", "../../../../contracts/scanner-limits-v2.ts", "../../src/v2/help.ts"],
4
- "sourcesContent": ["/**\n * The exclusion registry: an immutable, append-only list of workflow\n * identities withheld from every downstream artifact, keyed by reason and\n * date.\n *\n * **Identity.** A record is keyed by the same canonical id every `Entry`/\n * `Candidate` already carries \u2014 the 16-hex-character output of\n * `pipeline/acquire/entry-id.ts`'s `entryId(kind, ref, path)`. No second\n * identity scheme is introduced: a title or slug can change on a re-scrape or\n * a re-describe, but `entryId` is a hash of the source coordinates\n * (kind + ref + path) that never changes for the same workflow, which is\n * exactly the stability an exclusion needs \u2014 an exclusion recorded against a\n * workflow must still match it after its title, tags, or description change.\n *\n * **Registry vs. rights status.** This module answers a different question\n * than `rights.ts`'s `excluded` status does. The registry is the *cause* and\n * the *authority*: it is consulted at the earliest possible pipeline point\n * (immediately after acquisition, before rights are even decided \u2014 see\n * `pipeline/rights/exclusion-filter.ts`), so an excluded workflow is dropped\n * before it becomes a `Candidate` that anything downstream can see. A record\n * never becomes a full `Entry` at all in the ordinary path, so\n * `EntryRights.status === 'excluded'` is not how exclusion normally\n * propagates \u2014 it exists in the rights union as a second, independent gate:\n * if a record ever reaches rights decision or entry construction carrying an\n * id this registry recognizes, that code path can name it `excluded` instead\n * of guessing, rather than treating an already-answered question as still\n * open. Two gates, not one, because the registry's contents can change\n * (append-only growth) independently of any single pipeline run, and a\n * resumed or partially-cached run is exactly the case where the earlier gate\n * could be bypassed by stale state.\n *\n * **Immutable, append-only.** A registry value is frozen and exposes no\n * mutation method. Growing it means calling `appendExclusion`, which returns\n * a *new* registry with strictly one more record \u2014 it never edits or removes\n * an existing one. There is deliberately no `remove`/`update` API: a\n * retraction is a policy decision ([B]-blocked per\n * `openspec/changes/legal-rights-and-release-integrity/tasks.md`), not a data\n * operation this module should make easy to do quietly.\n */\n\nconst ENTRY_ID_PATTERN = /^[0-9a-f]{16}$/;\n\nexport type ExclusionRecord = {\n /** Canonical `entryId(kind, ref, path)` output \u2014 see module doc comment. */\n id: string;\n /** Human-readable, non-empty. Evidence, not a legal conclusion. */\n reason: string;\n /** ISO 8601 timestamp the exclusion was recorded. */\n excludedAt: string;\n};\n\nexport type ExclusionRegistry = {\n readonly records: readonly ExclusionRecord[];\n isExcluded(id: string): boolean;\n recordFor(id: string): ExclusionRecord | undefined;\n};\n\nfunction validateRecord(record: ExclusionRecord): void {\n if (!ENTRY_ID_PATTERN.test(record.id)) {\n throw new Error(\n `Exclusion registry record has a malformed id \"${record.id}\": expected the 16-hex-character ` +\n 'output of entryId(kind, ref, path), not a title, slug, or free-text identifier.',\n );\n }\n if (!record.reason.trim()) {\n throw new Error(`Exclusion registry record for id \"${record.id}\" has no reason recorded.`);\n }\n if (!Number.isFinite(Date.parse(record.excludedAt))) {\n throw new Error(\n `Exclusion registry record for id \"${record.id}\" has an invalid excludedAt timestamp: \"${record.excludedAt}\".`,\n );\n }\n}\n\n/**\n * Builds a frozen registry from a list of records, validating every one and\n * rejecting a duplicate id outright \u2014 the registry has exactly one record per\n * excluded identity, so two different reasons for the same id is a data bug,\n * not a merge to resolve silently.\n */\nexport function createExclusionRegistry(records: readonly ExclusionRecord[]): ExclusionRegistry {\n const seen = new Set<string>();\n for (const record of records) {\n validateRecord(record);\n if (seen.has(record.id)) {\n throw new Error(`Exclusion registry has more than one record for id \"${record.id}\".`);\n }\n seen.add(record.id);\n }\n const frozenRecords = Object.freeze(records.map((record) => Object.freeze({ ...record })));\n const byId = new Map(frozenRecords.map((record) => [record.id, record]));\n\n return Object.freeze({\n records: frozenRecords,\n isExcluded: (id: string) => byId.has(id),\n recordFor: (id: string) => byId.get(id),\n });\n}\n\n/** The starting registry: no exclusions recorded yet (owner decision [B]-blocked). */\nexport const EMPTY_EXCLUSION_REGISTRY: ExclusionRegistry = createExclusionRegistry([]);\n\n/**\n * Returns a *new* registry with `record` appended. Never mutates `registry`.\n * Throws if `record.id` is already present \u2014 append-only means every id is\n * recorded once, with one reason and one date; changing either requires a\n * conscious new record kept separately, not an overwrite of this one.\n */\nexport function appendExclusion(registry: ExclusionRegistry, record: ExclusionRecord): ExclusionRegistry {\n if (registry.isExcluded(record.id)) {\n throw new Error(\n `Id \"${record.id}\" is already recorded in the exclusion registry (reason: ` +\n `\"${registry.recordFor(record.id)?.reason}\"). Append-only: it cannot be re-added or overwritten.`,\n );\n }\n return createExclusionRegistry([...registry.records, record]);\n}\n", "/** SPDX ids we treat as redistributable. Static allowlist, deliberately short. */\nexport const REDISTRIBUTABLE_LICENCES: Record<string, true> = {\n 'MIT': true,\n 'Apache-2.0': true,\n 'BSD-2-Clause': true,\n 'BSD-3-Clause': true,\n 'ISC': true,\n 'CC0-1.0': true,\n 'Unlicense': true,\n};\n\n/** Hard limits. Shared by acquisition, the worker, and the browser scanner. */\nexport const LIMITS = {\n maxWorkflowBytes: 1_536_000,\n maxNodes: 5_000,\n scanTimeoutMs: 30_000,\n packSize: 250,\n shardSize: 2_000,\n maxPublicFindings: 20,\n} as const;\n", "import { LIMITS } from './limits';\n\n/**\n * The one versioned scanner-limits contract browser, worker, and pipeline\n * callers share. `LIMITS` (limits.ts) stays the source for the three numbers\n * it already owned (maxWorkflowBytes/maxNodes/scanTimeoutMs are read from it\n * below, not re-declared) so this cannot silently drift from it.\n */\nexport const SCANNER_LIMITS_SCHEMA_VERSION = 'scanner-limits-v1' as const;\n\nexport type ScannerLimitsV1 = {\n schemaVersion: typeof SCANNER_LIMITS_SCHEMA_VERSION;\n maxBytes: number;\n maxNodes: number;\n maxEdges: number;\n maxConnectionDepth: number;\n maxParameterDepth: number;\n maxParameterLeaves: number;\n workerDeadlineMs: number;\n};\n\nconst SCANNER_LIMITS_FIELDS = [\n 'schemaVersion',\n 'maxBytes',\n 'maxNodes',\n 'maxEdges',\n 'maxConnectionDepth',\n 'maxParameterDepth',\n 'maxParameterLeaves',\n 'workerDeadlineMs',\n] as const satisfies readonly (keyof ScannerLimitsV1)[];\n\nexport const DEFAULT_SCANNER_LIMITS: ScannerLimitsV1 = Object.freeze({\n schemaVersion: SCANNER_LIMITS_SCHEMA_VERSION,\n maxBytes: LIMITS.maxWorkflowBytes,\n maxNodes: LIMITS.maxNodes,\n maxEdges: 10_000,\n // Previously declared as a `workflow.ts` option but never wired to any\n // production caller (see scanner-contract-and-calibration tasks.md P1.1) \u2014\n // this is the first real default for it.\n maxConnectionDepth: 64,\n maxParameterDepth: 64,\n maxParameterLeaves: 10_000,\n workerDeadlineMs: LIMITS.scanTimeoutMs,\n});\n\nexport type ScannerLimitsValidationResult =\n | { ok: true; limits: ScannerLimitsV1 }\n | { ok: false; errors: string[] };\n\n/** Fails closed and reports every violation, not just the first, so a bad config is fully diagnosable. */\nexport function validateScannerLimits(candidate: unknown): ScannerLimitsValidationResult {\n if (typeof candidate !== 'object' || candidate === null || Array.isArray(candidate)) {\n return { ok: false, errors: ['limits must be an object'] };\n }\n\n const value = candidate as Record<string, unknown>;\n const errors: string[] = [];\n const knownFields = new Set<string>(SCANNER_LIMITS_FIELDS);\n\n for (const field of Object.keys(value)) {\n if (!knownFields.has(field)) errors.push(`unknown field: ${field}`);\n }\n\n if (value.schemaVersion !== SCANNER_LIMITS_SCHEMA_VERSION) {\n errors.push(`unsupported schemaVersion: ${String(value.schemaVersion)}`);\n }\n\n for (const field of SCANNER_LIMITS_FIELDS) {\n if (field === 'schemaVersion') continue;\n if (!(field in value)) {\n errors.push(`missing field: ${field}`);\n continue;\n }\n const raw = value[field];\n if (typeof raw !== 'number' || !Number.isInteger(raw) || raw < 0) {\n errors.push(`${field} must be a non-negative integer`);\n }\n }\n\n if (errors.length > 0) return { ok: false, errors };\n\n return {\n ok: true,\n limits: {\n schemaVersion: SCANNER_LIMITS_SCHEMA_VERSION,\n maxBytes: value.maxBytes as number,\n maxNodes: value.maxNodes as number,\n maxEdges: value.maxEdges as number,\n maxConnectionDepth: value.maxConnectionDepth as number,\n maxParameterDepth: value.maxParameterDepth as number,\n maxParameterLeaves: value.maxParameterLeaves as number,\n workerDeadlineMs: value.workerDeadlineMs as number,\n },\n };\n}\n\n/** Stable key order regardless of the input object's own construction order. */\nexport function serializeScannerLimits(limits: ScannerLimitsV1): string {\n return JSON.stringify(limits, SCANNER_LIMITS_FIELDS as unknown as string[]);\n}\n\n/** The exact option shape `parseWorkflowJson` accepts \u2014 one mapping, not one per caller. */\nexport function toWorkflowParseOptions(limits: ScannerLimitsV1): {\n maxBytes: number;\n maxNodes: number;\n maxEdges: number;\n maxConnectionDepth: number;\n maxParameterDepth: number;\n maxParameterLeaves: number;\n} {\n return {\n maxBytes: limits.maxBytes,\n maxNodes: limits.maxNodes,\n maxEdges: limits.maxEdges,\n maxConnectionDepth: limits.maxConnectionDepth,\n maxParameterDepth: limits.maxParameterDepth,\n maxParameterLeaves: limits.maxParameterLeaves,\n };\n}\n", "import {\n DEFAULT_SCANNER_LIMITS,\n type ScannerLimitsV1,\n} from './scanner-limits';\n\nexport const SCANNER_LIMITS_V2_SCHEMA_VERSION = 'scanner-limits-v2' as const;\n\nconst SCANNER_LIMITS_V2_NUMERIC_FIELDS = [\n 'maxBytes',\n 'maxNodes',\n 'maxEdges',\n 'maxConnectionDepth',\n 'maxParameterDepth',\n 'maxParameterLeaves',\n 'workerDeadlineMs',\n 'maxSyntaxNodes',\n 'maxDocumentDepth',\n 'maxMembersAndElements',\n 'maxRawEdges',\n 'maxDuplicateKeys',\n 'maxDiagnostics',\n 'maxStoredSourceSpans',\n 'maxSourceWork',\n 'maxExpressionTokens',\n 'maxCodeSyntaxNodes',\n 'maxGraphOperations',\n 'maxTaintStates',\n 'maxTraceStepsPerFinding',\n 'maxFindings',\n 'maxRedactionPatches',\n 'maxRedactedArtifactBytes',\n 'maxEvidenceBytes',\n 'maxSerializedResultBytes',\n 'maxRuleOperations',\n] as const;\n\nconst SCANNER_LIMITS_V2_FIELDS = [\n 'schemaVersion',\n ...SCANNER_LIMITS_V2_NUMERIC_FIELDS,\n] as const;\n\nexport type ScannerLimitsV2 = Omit<ScannerLimitsV1, 'schemaVersion'> & {\n schemaVersion: typeof SCANNER_LIMITS_V2_SCHEMA_VERSION;\n maxSyntaxNodes: number;\n maxDocumentDepth: number;\n maxMembersAndElements: number;\n maxRawEdges: number;\n maxDuplicateKeys: number;\n maxDiagnostics: number;\n maxStoredSourceSpans: number;\n maxSourceWork: number;\n maxExpressionTokens: number;\n maxCodeSyntaxNodes: number;\n maxGraphOperations: number;\n maxTaintStates: number;\n maxTraceStepsPerFinding: number;\n maxFindings: number;\n maxRedactionPatches: number;\n maxRedactedArtifactBytes: number;\n maxEvidenceBytes: number;\n maxSerializedResultBytes: number;\n maxRuleOperations: number;\n};\n\nexport const DEFAULT_SCANNER_LIMITS_V2: ScannerLimitsV2 = Object.freeze({\n ...DEFAULT_SCANNER_LIMITS,\n schemaVersion: SCANNER_LIMITS_V2_SCHEMA_VERSION,\n maxSyntaxNodes: 250_000,\n maxDocumentDepth: 256,\n maxMembersAndElements: 250_000,\n maxRawEdges: 50_000,\n maxDuplicateKeys: 1_000,\n maxDiagnostics: 1_000,\n maxStoredSourceSpans: 25_000,\n maxSourceWork: 1_000_000,\n maxExpressionTokens: 100_000,\n maxCodeSyntaxNodes: 250_000,\n maxGraphOperations: 1_000_000,\n maxTaintStates: 250_000,\n maxTraceStepsPerFinding: 100,\n maxFindings: 5_000,\n maxRedactionPatches: 10_000,\n maxRedactedArtifactBytes: 1_536_000,\n maxEvidenceBytes: 1_536_000,\n maxSerializedResultBytes: 5_000_000,\n maxRuleOperations: 1_000_000,\n});\n\nexport type ScannerLimitsV2ValidationResult =\n | { ok: true; limits: ScannerLimitsV2 }\n | { ok: false; errors: string[] };\n\nexport function validateScannerLimitsV2(candidate: unknown): ScannerLimitsV2ValidationResult {\n if (typeof candidate !== 'object' || candidate === null || Array.isArray(candidate)) {\n return { ok: false, errors: ['limits must be an object'] };\n }\n\n const value = candidate as Record<string, unknown>;\n const knownFields = new Set<string>(SCANNER_LIMITS_V2_FIELDS);\n const errors = Object.keys(value)\n .filter((field) => !knownFields.has(field))\n .sort()\n .map((field) => `unknown field: ${field}`);\n\n if (!Object.prototype.hasOwnProperty.call(value, 'schemaVersion')) {\n errors.push('missing field: schemaVersion');\n } else if (value.schemaVersion !== SCANNER_LIMITS_V2_SCHEMA_VERSION) {\n errors.push(`unsupported schemaVersion: ${String(value.schemaVersion)}`);\n }\n\n for (const field of SCANNER_LIMITS_V2_NUMERIC_FIELDS) {\n if (!Object.prototype.hasOwnProperty.call(value, field)) {\n errors.push(`missing field: ${field}`);\n continue;\n }\n const raw = value[field];\n if (!Number.isSafeInteger(raw) || (raw as number) < 0) {\n errors.push(`${field} must be a non-negative safe integer`);\n } else if (field === 'maxDiagnostics' && (raw as number) < 1) {\n errors.push('maxDiagnostics must be at least 1');\n }\n }\n\n if (errors.length > 0) return { ok: false, errors };\n return {\n ok: true,\n limits: {\n schemaVersion: SCANNER_LIMITS_V2_SCHEMA_VERSION,\n ...Object.fromEntries(SCANNER_LIMITS_V2_NUMERIC_FIELDS.map((field) => [field, value[field]])),\n } as ScannerLimitsV2,\n };\n}\n\nexport function toWorkflowParseOptionsV2(limits: ScannerLimitsV2): {\n maxBytes: number;\n maxNodes: number;\n maxEdges: number;\n maxConnectionDepth: number;\n maxParameterDepth: number;\n maxParameterLeaves: number;\n} {\n return {\n maxBytes: limits.maxBytes,\n maxNodes: limits.maxNodes,\n maxEdges: limits.maxEdges,\n maxConnectionDepth: limits.maxConnectionDepth,\n maxParameterDepth: limits.maxParameterDepth,\n maxParameterLeaves: limits.maxParameterLeaves,\n };\n}\n", "import { DEFAULT_SCANNER_LIMITS_V2 } from '@hub/contracts';\n\nexport const V2_COMMANDS = Object.freeze([\n 'scan', 'rules', 'explain', 'profiles', 'validate-config', 'version',\n] as const);\nexport type V2CommandName = (typeof V2_COMMANDS)[number];\n\nexport type V2OptionName =\n | '--help' | '--schema' | '--format' | '--profile' | '--fail-on' | '--require-complete'\n | '--max-concurrency' | '--concurrency' | '--timeout' | '--output' | '--config'\n | '--rules' | '--catalogue' | '--limits' | '--target' | '--n8n-version' | '--edition'\n | '--deployment' | '--role' | '--task-runner' | '--community-package' | '--configuration'\n | '--include' | '--exclude' | '--allow-unsigned-development-rules' | '--no-color';\n\nexport type V2HelpOption = Readonly<{\n name: V2OptionName;\n value: string;\n commands: readonly V2CommandName[];\n description: string;\n default?: string;\n}>;\n\nconst SCAN = Object.freeze(['scan'] as const);\nconst ALL = V2_COMMANDS;\n\nexport const V2_OPTIONS: readonly V2HelpOption[] = Object.freeze([\n { name: '--help', value: '', commands: ALL, description: 'Show deterministic help for the selected v2 command.' },\n { name: '--schema', value: '<v2>', commands: ALL, description: 'Select the versioned v2 grammar.' },\n { name: '--format', value: '<json|ndjson|sarif|table>', commands: ALL, description: 'Select the output format.', default: 'json for scan; json for metadata' },\n { name: '--profile', value: '<beta-default>', commands: SCAN, description: 'Select the admitted analysis profile.', default: 'beta-default' },\n { name: '--fail-on', value: '<CRITICAL|HIGH|MEDIUM|LOW|INFO>', commands: SCAN, description: 'Fail when a finding meets or exceeds this severity.', default: 'INFO' },\n { name: '--require-complete', value: '', commands: SCAN, description: 'Treat DEGRADED analysis as a failure.', default: 'false' },\n { name: '--max-concurrency', value: '<1..16>', commands: SCAN, description: 'Bound concurrent inputs; --concurrency is an equivalent spelling.', default: '1' },\n { name: '--concurrency', value: '<1..16>', commands: SCAN, description: 'Bound concurrent inputs; --max-concurrency is an equivalent spelling.', default: '1' },\n { name: '--timeout', value: '<1..2147483647>', commands: SCAN, description: 'Set the per-input timeout in milliseconds.', default: '30000 (workerDeadlineMs)' },\n { name: '--output', value: '<local-file|->', commands: SCAN, description: 'Write to a local file atomically, or use - for stdout.', default: 'stdout' },\n { name: '--config', value: '<local-file>', commands: SCAN, description: 'Load one explicit versioned config file.' },\n { name: '--rules', value: '<builtin>', commands: SCAN, description: 'Select the embedded built-in ruleset; the only admitted authority.', default: 'builtin' },\n { name: '--catalogue', value: '<builtin>', commands: SCAN, description: 'Select the embedded semantic catalogue.', default: 'builtin' },\n { name: '--limits', value: '<local-file|builtin>', commands: SCAN, description: 'Select embedded or local scanner limits.', default: 'builtin' },\n { name: '--target', value: '<local-file>', commands: SCAN, description: 'Load a local target context.' },\n { name: '--n8n-version', value: '<semver>', commands: SCAN, description: 'Set the n8n version fact.' },\n { name: '--edition', value: '<COMMUNITY|ENTERPRISE|CLOUD|UNKNOWN>', commands: SCAN, description: 'Set the deployment edition fact.', default: 'UNKNOWN' },\n { name: '--deployment', value: '<SELF_HOSTED|CLOUD|UNKNOWN>', commands: SCAN, description: 'Set the deployment fact.', default: 'UNKNOWN' },\n { name: '--role', value: '<OWNER|ADMIN|MEMBER|UNKNOWN>', commands: SCAN, description: 'Set the operator role fact.', default: 'UNKNOWN' },\n { name: '--task-runner', value: '<LEGACY|NATIVE|UNKNOWN>', commands: SCAN, description: 'Set the task-runner fact.', default: 'UNKNOWN' },\n { name: '--community-package', value: '<package>', commands: SCAN, description: 'Record a community package in target context; repeatable.' },\n { name: '--configuration', value: '<name>', commands: SCAN, description: 'Record a configuration fact; repeatable.' },\n { name: '--include', value: '<posix-glob>', commands: SCAN, description: 'Replace config includes with a case-sensitive POSIX glob; repeatable.', default: '**/*.json' },\n { name: '--exclude', value: '<posix-glob>', commands: SCAN, description: 'Replace config excludes with a case-sensitive POSIX glob; repeatable.', default: '.git/**, node_modules/**' },\n { name: '--allow-unsigned-development-rules', value: '', commands: SCAN, description: 'Refused: external rule packs are not admitted, so no pack can be selected.', default: 'false' },\n { name: '--no-color', value: '', commands: SCAN, description: 'Disable color in human output.', default: 'false (also disabled for non-TTY)' },\n]);\n\nexport type V2LimitName =\n | 'maxBytes' | 'maxNodes' | 'maxEdges' | 'maxConnectionDepth' | 'maxParameterDepth' | 'maxParameterLeaves'\n | 'workerDeadlineMs' | 'maxSyntaxNodes' | 'maxDocumentDepth' | 'maxMembersAndElements' | 'maxRawEdges'\n | 'maxDuplicateKeys' | 'maxDiagnostics' | 'maxStoredSourceSpans' | 'maxExpressionTokens' | 'maxCodeSyntaxNodes'\n | 'maxSourceWork'\n | 'maxGraphOperations' | 'maxTaintStates' | 'maxTraceStepsPerFinding' | 'maxFindings' | 'maxRedactionPatches'\n | 'maxRedactedArtifactBytes' | 'maxEvidenceBytes' | 'maxSerializedResultBytes' | 'maxRuleOperations';\n\nexport const V2_LIMITS: Readonly<Record<V2LimitName, number>> = Object.freeze({\n maxBytes: DEFAULT_SCANNER_LIMITS_V2.maxBytes,\n maxNodes: DEFAULT_SCANNER_LIMITS_V2.maxNodes,\n maxEdges: DEFAULT_SCANNER_LIMITS_V2.maxEdges,\n maxConnectionDepth: DEFAULT_SCANNER_LIMITS_V2.maxConnectionDepth,\n maxParameterDepth: DEFAULT_SCANNER_LIMITS_V2.maxParameterDepth,\n maxParameterLeaves: DEFAULT_SCANNER_LIMITS_V2.maxParameterLeaves,\n workerDeadlineMs: DEFAULT_SCANNER_LIMITS_V2.workerDeadlineMs,\n maxSyntaxNodes: DEFAULT_SCANNER_LIMITS_V2.maxSyntaxNodes,\n maxDocumentDepth: DEFAULT_SCANNER_LIMITS_V2.maxDocumentDepth,\n maxMembersAndElements: DEFAULT_SCANNER_LIMITS_V2.maxMembersAndElements,\n maxRawEdges: DEFAULT_SCANNER_LIMITS_V2.maxRawEdges,\n maxDuplicateKeys: DEFAULT_SCANNER_LIMITS_V2.maxDuplicateKeys,\n maxDiagnostics: DEFAULT_SCANNER_LIMITS_V2.maxDiagnostics,\n maxStoredSourceSpans: DEFAULT_SCANNER_LIMITS_V2.maxStoredSourceSpans,\n maxSourceWork: DEFAULT_SCANNER_LIMITS_V2.maxSourceWork,\n maxExpressionTokens: DEFAULT_SCANNER_LIMITS_V2.maxExpressionTokens,\n maxCodeSyntaxNodes: DEFAULT_SCANNER_LIMITS_V2.maxCodeSyntaxNodes,\n maxGraphOperations: DEFAULT_SCANNER_LIMITS_V2.maxGraphOperations,\n maxTaintStates: DEFAULT_SCANNER_LIMITS_V2.maxTaintStates,\n maxTraceStepsPerFinding: DEFAULT_SCANNER_LIMITS_V2.maxTraceStepsPerFinding,\n maxFindings: DEFAULT_SCANNER_LIMITS_V2.maxFindings,\n maxRedactionPatches: DEFAULT_SCANNER_LIMITS_V2.maxRedactionPatches,\n maxRedactedArtifactBytes: DEFAULT_SCANNER_LIMITS_V2.maxRedactedArtifactBytes,\n maxEvidenceBytes: DEFAULT_SCANNER_LIMITS_V2.maxEvidenceBytes,\n maxSerializedResultBytes: DEFAULT_SCANNER_LIMITS_V2.maxSerializedResultBytes,\n maxRuleOperations: DEFAULT_SCANNER_LIMITS_V2.maxRuleOperations,\n});\n\n/** Limits whose operation is development-only and unwired; see V2_CANONICAL_CLAIMS below. */\nconst UNWIRED_LIMITS: Readonly<Record<string, true>> = Object.freeze({\n maxRedactionPatches: true,\n maxRedactedArtifactBytes: true,\n});\n\nexport const V2_EXIT_CODES = Object.freeze([0, 1, 2, 3, 4, 130] as const);\nexport type V2ExitCode = (typeof V2_EXIT_CODES)[number];\nexport const V2_EXIT_REASONS = Object.freeze([\n 'CANCELLED', 'INTERNAL', 'USAGE', 'FINDING_THRESHOLD', 'REQUIRE_COMPLETE', 'ITEM_FAILURE', 'UNDER_THRESHOLD',\n] as const);\nexport type V2ExitReason = (typeof V2_EXIT_REASONS)[number];\n\nexport const V2_CANONICAL_CLAIMS = Object.freeze([\n 'v2 is offline and never initiates network requests (HTTP, DNS, sockets, telemetry, or remote imports).',\n 'Labels and output identities never contain absolute paths or workflow source content.',\n 'File output uses a same-directory temporary file and atomic replace; failures and cancellation preserve the prior file.',\n 'beta-default@2.0.0 is admitted; beta-hardened@2.0.0 is defined but not admitted or selectable.',\n 'No shipped path produces a redacted artifact: maxRedactionPatches and maxRedactedArtifactBytes bound redaction-v2, which is development-only and unwired.',\n 'No shipped path admits an external rule pack: --rules and --catalogue accept only builtin, and --allow-unsigned-development-rules is refused because Ed25519 trust-store admission is deferred.',\n 'Published on npm as sfscanner.',\n] as const);\n\nexport type V2CommandHelp = Readonly<{ command: V2CommandName; usage: readonly string[]; text: string }>;\n\nfunction optionLine(option: V2HelpOption, command?: V2CommandName): string {\n const value = option.name === '--format' && command !== undefined && command !== 'scan' ? '<json|table>' : option.value;\n const syntax = value ? ` ${value}` : '';\n const defaultText = option.default ? ` Default: ${option.default}.` : '';\n return ` ${option.name}${syntax} ${option.description}${defaultText}`;\n}\nfunction renderCommand(command: V2CommandName): V2CommandHelp {\n const usage = command === 'scan'\n ? [`sfscanner --schema v2 scan <file|directory|-> [options]`]\n : command === 'explain'\n ? [`sfscanner --schema v2 explain <rule-id> [--format json|table]`]\n : command === 'validate-config'\n ? [`sfscanner --schema v2 validate-config <local-file> [--format json|table]`]\n : [`sfscanner --schema v2 ${command} [--format json|table]`];\n const options = V2_OPTIONS.filter((option) => option.commands.includes(command));\n const lines = [`sfscanner v2 ${command}`, '', 'Usage:', ...usage, '', 'Options:', ...options.map((option) => optionLine(option, command))];\n if (command === 'scan') lines.push('', 'Inputs are sorted by normalized relative label; stdin (-) cannot join a batch.');\n if (command === 'profiles') lines.push('', 'beta-default is admitted. beta-hardened is defined but not admitted or selectable.');\n if (command === 'validate-config') lines.push('', 'Only the explicitly supplied local config file is read.');\n return Object.freeze({ command, usage: Object.freeze(usage), text: `${lines.join('\\n')}\\n` });\n}\n\nexport const COMMAND_HELP_V2: Readonly<Record<V2CommandName, string>> = Object.freeze(\n Object.fromEntries(V2_COMMANDS.map((command) => [command, renderCommand(command).text])) as Record<V2CommandName, string>,\n);\n\nconst generalLines = [\n 'sfscanner \u2014 deterministic offline n8n workflow scanner (v2)', '', 'Usage:',\n ' sfscanner --schema v2 scan <file|directory|-> [options]',\n ' sfscanner --schema v2 rules [--format json|table]',\n ' sfscanner --schema v2 explain <rule-id> [--format json|table]',\n ' sfscanner --schema v2 profiles [--format json|table]',\n ' sfscanner --schema v2 validate-config <local-file> [--format json|table]',\n ' sfscanner --schema v2 version [--format json|table]', '', 'Commands:',\n 'Defaults:', ' format: json; profile: beta-default; fail-on: INFO; require-complete: false; concurrency: 1 (maximum 16); timeout: 30000ms.',\n ' include: **/*.json; exclude: .git/**, node_modules/**; rules/catalogue/limits: builtin.',\n ' target facts: edition UNKNOWN; deployment UNKNOWN; role UNKNOWN; task-runner UNKNOWN.',\n '', 'Profiles:', ' beta-default@2.0.0: admitted and selectable.',\n ' beta-hardened@2.0.0: defined but not admitted or selectable.', '', 'Limits:',\n ...Object.entries(V2_LIMITS).map(([name, value]) => ` ${name}=${value}${UNWIRED_LIMITS[name] ? ' (development-only, unwired)' : ''}`), '',\n 'Exit codes and precedence:', ' 130 CANCELLED; 4 INTERNAL; 2 USAGE; 1 FINDING_THRESHOLD or REQUIRE_COMPLETE; 3 ITEM_FAILURE; 0 UNDER_THRESHOLD.',\n ' Pre-scan usage/configuration/admission failures emit no machine envelope and exit 2; stdout cancellation emits a valid envelope and exits 130.',\n ' When finding threshold and require-complete both fire, FINDING_THRESHOLD wins. Indeterminate never exits 0.', '',\n ...V2_CANONICAL_CLAIMS,\n];\nexport const GENERAL_HELP_V2 = `${generalLines.join('\\n')}\\n`;\nexport const HELP_TEXT_V2 = GENERAL_HELP_V2;\n\nexport function getHelpV2(command?: V2CommandName): string {\n return command === undefined ? GENERAL_HELP_V2 : COMMAND_HELP_V2[command];\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * The exclusion registry: an immutable, append-only list of workflow\n * identities withheld from every downstream artifact, keyed by reason and\n * date.\n *\n * **Identity.** A record is keyed by the same canonical id every `Entry`/\n * `Candidate` already carries \u2014 the 16-hex-character output of\n * `pipeline/acquire/entry-id.ts`'s `entryId(kind, ref, path)`. No second\n * identity scheme is introduced: a title or slug can change on a re-scrape or\n * a re-describe, but `entryId` is a hash of the source coordinates\n * (kind + ref + path) that never changes for the same workflow, which is\n * exactly the stability an exclusion needs \u2014 an exclusion recorded against a\n * workflow must still match it after its title, tags, or description change.\n *\n * **Registry vs. rights status.** This module answers a different question\n * than `rights.ts`'s `excluded` status does. The registry is the *cause* and\n * the *authority*: it is consulted at the earliest possible pipeline point\n * (immediately after acquisition, before rights are even decided \u2014 see\n * `pipeline/rights/exclusion-filter.ts`), so an excluded workflow is dropped\n * before it becomes a `Candidate` that anything downstream can see. A record\n * never becomes a full `Entry` at all in the ordinary path, so\n * `EntryRights.status === 'excluded'` is not how exclusion normally\n * propagates \u2014 it exists in the rights union as a second, independent gate:\n * if a record ever reaches rights decision or entry construction carrying an\n * id this registry recognizes, that code path can name it `excluded` instead\n * of guessing, rather than treating an already-answered question as still\n * open. Two gates, not one, because the registry's contents can change\n * (append-only growth) independently of any single pipeline run, and a\n * resumed or partially-cached run is exactly the case where the earlier gate\n * could be bypassed by stale state.\n *\n * **Immutable, append-only.** A registry value is frozen and exposes no\n * mutation method. Growing it means calling `appendExclusion`, which returns\n * a *new* registry with strictly one more record \u2014 it never edits or removes\n * an existing one. There is deliberately no `remove`/`update` API: a\n * retraction is a policy decision ([B]-blocked per\n * `openspec/changes/legal-rights-and-release-integrity/tasks.md`), not a data\n * operation this module should make easy to do quietly.\n */\n\nconst ENTRY_ID_PATTERN = /^[0-9a-f]{16}$/;\n\nexport type ExclusionRecord = {\n /** Canonical `entryId(kind, ref, path)` output \u2014 see module doc comment. */\n id: string;\n /** Human-readable, non-empty. Evidence, not a legal conclusion. */\n reason: string;\n /** ISO 8601 timestamp the exclusion was recorded. */\n excludedAt: string;\n};\n\nexport type ExclusionRegistry = {\n readonly records: readonly ExclusionRecord[];\n isExcluded(id: string): boolean;\n recordFor(id: string): ExclusionRecord | undefined;\n};\n\nfunction validateRecord(record: ExclusionRecord): void {\n if (!ENTRY_ID_PATTERN.test(record.id)) {\n throw new Error(\n `Exclusion registry record has a malformed id \"${record.id}\": expected the 16-hex-character ` +\n 'output of entryId(kind, ref, path), not a title, slug, or free-text identifier.',\n );\n }\n if (!record.reason.trim()) {\n throw new Error(`Exclusion registry record for id \"${record.id}\" has no reason recorded.`);\n }\n if (!Number.isFinite(Date.parse(record.excludedAt))) {\n throw new Error(\n `Exclusion registry record for id \"${record.id}\" has an invalid excludedAt timestamp: \"${record.excludedAt}\".`,\n );\n }\n}\n\n/**\n * Builds a frozen registry from a list of records, validating every one and\n * rejecting a duplicate id outright \u2014 the registry has exactly one record per\n * excluded identity, so two different reasons for the same id is a data bug,\n * not a merge to resolve silently.\n */\nexport function createExclusionRegistry(records: readonly ExclusionRecord[]): ExclusionRegistry {\n const seen = new Set<string>();\n for (const record of records) {\n validateRecord(record);\n if (seen.has(record.id)) {\n throw new Error(`Exclusion registry has more than one record for id \"${record.id}\".`);\n }\n seen.add(record.id);\n }\n const frozenRecords = Object.freeze(records.map((record) => Object.freeze({ ...record })));\n const byId = new Map(frozenRecords.map((record) => [record.id, record]));\n\n return Object.freeze({\n records: frozenRecords,\n isExcluded: (id: string) => byId.has(id),\n recordFor: (id: string) => byId.get(id),\n });\n}\n\n/** The starting registry: no exclusions recorded yet (owner decision [B]-blocked). */\nexport const EMPTY_EXCLUSION_REGISTRY: ExclusionRegistry = createExclusionRegistry([]);\n\n/**\n * Returns a *new* registry with `record` appended. Never mutates `registry`.\n * Throws if `record.id` is already present \u2014 append-only means every id is\n * recorded once, with one reason and one date; changing either requires a\n * conscious new record kept separately, not an overwrite of this one.\n */\nexport function appendExclusion(registry: ExclusionRegistry, record: ExclusionRecord): ExclusionRegistry {\n if (registry.isExcluded(record.id)) {\n throw new Error(\n `Id \"${record.id}\" is already recorded in the exclusion registry (reason: ` +\n `\"${registry.recordFor(record.id)?.reason}\"). Append-only: it cannot be re-added or overwritten.`,\n );\n }\n return createExclusionRegistry([...registry.records, record]);\n}\n", "/** SPDX ids we treat as redistributable. Static allowlist, deliberately short. */\nexport const REDISTRIBUTABLE_LICENCES: Record<string, true> = {\n 'MIT': true,\n 'Apache-2.0': true,\n 'BSD-2-Clause': true,\n 'BSD-3-Clause': true,\n 'ISC': true,\n 'CC0-1.0': true,\n 'Unlicense': true,\n};\n\n/** Hard limits. Shared by acquisition, the worker, and the browser scanner. */\nexport const LIMITS = {\n maxWorkflowBytes: 1_536_000,\n maxNodes: 5_000,\n scanTimeoutMs: 30_000,\n packSize: 250,\n shardSize: 2_000,\n maxPublicFindings: 20,\n} as const;\n", "import { LIMITS } from './limits';\n\n/**\n * The one versioned scanner-limits contract browser, worker, and pipeline\n * callers share. `LIMITS` (limits.ts) stays the source for the three numbers\n * it already owned (maxWorkflowBytes/maxNodes/scanTimeoutMs are read from it\n * below, not re-declared) so this cannot silently drift from it.\n */\nexport const SCANNER_LIMITS_SCHEMA_VERSION = 'scanner-limits-v1' as const;\n\nexport type ScannerLimitsV1 = {\n schemaVersion: typeof SCANNER_LIMITS_SCHEMA_VERSION;\n maxBytes: number;\n maxNodes: number;\n maxEdges: number;\n maxConnectionDepth: number;\n maxParameterDepth: number;\n maxParameterLeaves: number;\n workerDeadlineMs: number;\n};\n\nconst SCANNER_LIMITS_FIELDS = [\n 'schemaVersion',\n 'maxBytes',\n 'maxNodes',\n 'maxEdges',\n 'maxConnectionDepth',\n 'maxParameterDepth',\n 'maxParameterLeaves',\n 'workerDeadlineMs',\n] as const satisfies readonly (keyof ScannerLimitsV1)[];\n\nexport const DEFAULT_SCANNER_LIMITS: ScannerLimitsV1 = Object.freeze({\n schemaVersion: SCANNER_LIMITS_SCHEMA_VERSION,\n maxBytes: LIMITS.maxWorkflowBytes,\n maxNodes: LIMITS.maxNodes,\n maxEdges: 10_000,\n // Previously declared as a `workflow.ts` option but never wired to any\n // production caller (see scanner-contract-and-calibration tasks.md P1.1) \u2014\n // this is the first real default for it.\n maxConnectionDepth: 64,\n maxParameterDepth: 64,\n maxParameterLeaves: 10_000,\n workerDeadlineMs: LIMITS.scanTimeoutMs,\n});\n\nexport type ScannerLimitsValidationResult =\n | { ok: true; limits: ScannerLimitsV1 }\n | { ok: false; errors: string[] };\n\n/** Fails closed and reports every violation, not just the first, so a bad config is fully diagnosable. */\nexport function validateScannerLimits(candidate: unknown): ScannerLimitsValidationResult {\n if (typeof candidate !== 'object' || candidate === null || Array.isArray(candidate)) {\n return { ok: false, errors: ['limits must be an object'] };\n }\n\n const value = candidate as Record<string, unknown>;\n const errors: string[] = [];\n const knownFields = new Set<string>(SCANNER_LIMITS_FIELDS);\n\n for (const field of Object.keys(value)) {\n if (!knownFields.has(field)) errors.push(`unknown field: ${field}`);\n }\n\n if (value.schemaVersion !== SCANNER_LIMITS_SCHEMA_VERSION) {\n errors.push(`unsupported schemaVersion: ${String(value.schemaVersion)}`);\n }\n\n for (const field of SCANNER_LIMITS_FIELDS) {\n if (field === 'schemaVersion') continue;\n if (!(field in value)) {\n errors.push(`missing field: ${field}`);\n continue;\n }\n const raw = value[field];\n if (typeof raw !== 'number' || !Number.isInteger(raw) || raw < 0) {\n errors.push(`${field} must be a non-negative integer`);\n }\n }\n\n if (errors.length > 0) return { ok: false, errors };\n\n return {\n ok: true,\n limits: {\n schemaVersion: SCANNER_LIMITS_SCHEMA_VERSION,\n maxBytes: value.maxBytes as number,\n maxNodes: value.maxNodes as number,\n maxEdges: value.maxEdges as number,\n maxConnectionDepth: value.maxConnectionDepth as number,\n maxParameterDepth: value.maxParameterDepth as number,\n maxParameterLeaves: value.maxParameterLeaves as number,\n workerDeadlineMs: value.workerDeadlineMs as number,\n },\n };\n}\n\n/** Stable key order regardless of the input object's own construction order. */\nexport function serializeScannerLimits(limits: ScannerLimitsV1): string {\n return JSON.stringify(limits, SCANNER_LIMITS_FIELDS as unknown as string[]);\n}\n\n/** The exact option shape `parseWorkflowJson` accepts \u2014 one mapping, not one per caller. */\nexport function toWorkflowParseOptions(limits: ScannerLimitsV1): {\n maxBytes: number;\n maxNodes: number;\n maxEdges: number;\n maxConnectionDepth: number;\n maxParameterDepth: number;\n maxParameterLeaves: number;\n} {\n return {\n maxBytes: limits.maxBytes,\n maxNodes: limits.maxNodes,\n maxEdges: limits.maxEdges,\n maxConnectionDepth: limits.maxConnectionDepth,\n maxParameterDepth: limits.maxParameterDepth,\n maxParameterLeaves: limits.maxParameterLeaves,\n };\n}\n", "import {\n DEFAULT_SCANNER_LIMITS,\n type ScannerLimitsV1,\n} from './scanner-limits';\n\nexport const SCANNER_LIMITS_V2_SCHEMA_VERSION = 'scanner-limits-v2' as const;\n\nconst SCANNER_LIMITS_V2_NUMERIC_FIELDS = [\n 'maxBytes',\n 'maxNodes',\n 'maxEdges',\n 'maxConnectionDepth',\n 'maxParameterDepth',\n 'maxParameterLeaves',\n 'workerDeadlineMs',\n 'maxSyntaxNodes',\n 'maxDocumentDepth',\n 'maxMembersAndElements',\n 'maxRawEdges',\n 'maxDuplicateKeys',\n 'maxDiagnostics',\n 'maxStoredSourceSpans',\n 'maxSourceWork',\n 'maxExpressionTokens',\n 'maxCodeSyntaxNodes',\n 'maxGraphOperations',\n 'maxTaintStates',\n 'maxTraceStepsPerFinding',\n 'maxFindings',\n 'maxRedactionPatches',\n 'maxRedactedArtifactBytes',\n 'maxEvidenceBytes',\n 'maxSerializedResultBytes',\n 'maxRuleOperations',\n] as const;\n\nconst SCANNER_LIMITS_V2_FIELDS = [\n 'schemaVersion',\n ...SCANNER_LIMITS_V2_NUMERIC_FIELDS,\n] as const;\n\nexport type ScannerLimitsV2 = Omit<ScannerLimitsV1, 'schemaVersion'> & {\n schemaVersion: typeof SCANNER_LIMITS_V2_SCHEMA_VERSION;\n maxSyntaxNodes: number;\n maxDocumentDepth: number;\n maxMembersAndElements: number;\n maxRawEdges: number;\n maxDuplicateKeys: number;\n maxDiagnostics: number;\n maxStoredSourceSpans: number;\n maxSourceWork: number;\n maxExpressionTokens: number;\n maxCodeSyntaxNodes: number;\n maxGraphOperations: number;\n maxTaintStates: number;\n maxTraceStepsPerFinding: number;\n maxFindings: number;\n maxRedactionPatches: number;\n maxRedactedArtifactBytes: number;\n maxEvidenceBytes: number;\n maxSerializedResultBytes: number;\n maxRuleOperations: number;\n};\n\nexport const DEFAULT_SCANNER_LIMITS_V2: ScannerLimitsV2 = Object.freeze({\n ...DEFAULT_SCANNER_LIMITS,\n schemaVersion: SCANNER_LIMITS_V2_SCHEMA_VERSION,\n maxSyntaxNodes: 250_000,\n maxDocumentDepth: 256,\n maxMembersAndElements: 250_000,\n maxRawEdges: 50_000,\n maxDuplicateKeys: 1_000,\n maxDiagnostics: 1_000,\n maxStoredSourceSpans: 25_000,\n maxSourceWork: 1_000_000,\n maxExpressionTokens: 100_000,\n maxCodeSyntaxNodes: 250_000,\n maxGraphOperations: 1_000_000,\n maxTaintStates: 250_000,\n maxTraceStepsPerFinding: 100,\n maxFindings: 5_000,\n maxRedactionPatches: 10_000,\n maxRedactedArtifactBytes: 1_536_000,\n maxEvidenceBytes: 1_536_000,\n maxSerializedResultBytes: 5_000_000,\n maxRuleOperations: 1_000_000,\n});\n\nexport type ScannerLimitsV2ValidationResult =\n | { ok: true; limits: ScannerLimitsV2 }\n | { ok: false; errors: string[] };\n\nexport type AdmittedLimitStateV2 = 'WIRED' | 'DORMANT' | 'UNWIRED';\nexport type ScannerLimitNameV2 = (typeof SCANNER_LIMITS_V2_NUMERIC_FIELDS)[number];\n\n/**\n * The one authority every help surface and usage row reads. `WIRED` is consumed by the admitted\n * beta-default path, `DORMANT` only by an unadmitted profile (the hardened code, value-flow, taint\n * and graph analyzers), and `UNWIRED` by no shipped path (redaction-v2 is development-only).\n * `workerDeadlineMs` is wired because the CLI worker host enforces it.\n */\nexport const ADMITTED_LIMIT_STATES: Readonly<Record<ScannerLimitNameV2, AdmittedLimitStateV2>> = Object.freeze({\n maxBytes: 'WIRED',\n maxNodes: 'WIRED',\n maxEdges: 'WIRED',\n maxConnectionDepth: 'WIRED',\n maxParameterDepth: 'WIRED',\n maxParameterLeaves: 'WIRED',\n workerDeadlineMs: 'WIRED',\n maxSyntaxNodes: 'WIRED',\n maxDocumentDepth: 'WIRED',\n maxMembersAndElements: 'WIRED',\n maxRawEdges: 'WIRED',\n maxDuplicateKeys: 'WIRED',\n maxDiagnostics: 'WIRED',\n maxStoredSourceSpans: 'WIRED',\n maxSourceWork: 'WIRED',\n maxExpressionTokens: 'DORMANT',\n maxCodeSyntaxNodes: 'DORMANT',\n maxGraphOperations: 'DORMANT',\n maxTaintStates: 'DORMANT',\n maxTraceStepsPerFinding: 'DORMANT',\n maxFindings: 'WIRED',\n maxRedactionPatches: 'UNWIRED',\n maxRedactedArtifactBytes: 'UNWIRED',\n maxEvidenceBytes: 'WIRED',\n maxSerializedResultBytes: 'WIRED',\n maxRuleOperations: 'WIRED',\n});\n\n/**\n * The subset with a real per-scan counter. The narrow tuple is deliberate: a wide annotation would\n * make every one of the 26 names assignable, so the 13-key counter object below would not typecheck.\n * A budget without a counter is enforced where it is consumed but has no honest `usage` row.\n */\nexport const MEASURED_LIMIT_IDS = [\n 'maxBytes', 'maxNodes', 'maxEdges', 'maxConnectionDepth', 'maxParameterDepth',\n 'maxParameterLeaves', 'maxSyntaxNodes', 'maxRawEdges', 'maxDuplicateKeys',\n 'maxDiagnostics', 'maxStoredSourceSpans', 'maxSourceWork', 'maxFindings',\n] as const satisfies readonly ScannerLimitNameV2[];\n\nexport function validateScannerLimitsV2(candidate: unknown): ScannerLimitsV2ValidationResult {\n if (typeof candidate !== 'object' || candidate === null || Array.isArray(candidate)) {\n return { ok: false, errors: ['limits must be an object'] };\n }\n\n const value = candidate as Record<string, unknown>;\n const knownFields = new Set<string>(SCANNER_LIMITS_V2_FIELDS);\n const errors = Object.keys(value)\n .filter((field) => !knownFields.has(field))\n .sort()\n .map((field) => `unknown field: ${field}`);\n\n if (!Object.prototype.hasOwnProperty.call(value, 'schemaVersion')) {\n errors.push('missing field: schemaVersion');\n } else if (value.schemaVersion !== SCANNER_LIMITS_V2_SCHEMA_VERSION) {\n errors.push(`unsupported schemaVersion: ${String(value.schemaVersion)}`);\n }\n\n for (const field of SCANNER_LIMITS_V2_NUMERIC_FIELDS) {\n if (!Object.prototype.hasOwnProperty.call(value, field)) {\n errors.push(`missing field: ${field}`);\n continue;\n }\n const raw = value[field];\n if (!Number.isSafeInteger(raw) || (raw as number) < 0) {\n errors.push(`${field} must be a non-negative safe integer`);\n } else if (field === 'maxDiagnostics' && (raw as number) < 1) {\n errors.push('maxDiagnostics must be at least 1');\n }\n }\n\n if (errors.length > 0) return { ok: false, errors };\n return {\n ok: true,\n limits: {\n schemaVersion: SCANNER_LIMITS_V2_SCHEMA_VERSION,\n ...Object.fromEntries(SCANNER_LIMITS_V2_NUMERIC_FIELDS.map((field) => [field, value[field]])),\n } as ScannerLimitsV2,\n };\n}\n\nexport function toWorkflowParseOptionsV2(limits: ScannerLimitsV2): {\n maxBytes: number;\n maxNodes: number;\n maxEdges: number;\n maxConnectionDepth: number;\n maxParameterDepth: number;\n maxParameterLeaves: number;\n} {\n return {\n maxBytes: limits.maxBytes,\n maxNodes: limits.maxNodes,\n maxEdges: limits.maxEdges,\n maxConnectionDepth: limits.maxConnectionDepth,\n maxParameterDepth: limits.maxParameterDepth,\n maxParameterLeaves: limits.maxParameterLeaves,\n };\n}\n", "import { ADMITTED_LIMIT_STATES, DEFAULT_SCANNER_LIMITS_V2, MEASURED_LIMIT_IDS } from '@hub/contracts';\n\nexport const V2_COMMANDS = Object.freeze([\n 'scan', 'rules', 'explain', 'profiles', 'validate-config', 'version',\n] as const);\nexport type V2CommandName = (typeof V2_COMMANDS)[number];\n\nexport type V2OptionName =\n | '--help' | '--schema' | '--format' | '--profile' | '--fail-on' | '--require-complete'\n | '--max-concurrency' | '--concurrency' | '--timeout' | '--output' | '--config'\n | '--rules' | '--catalogue' | '--limits' | '--target' | '--n8n-version' | '--edition'\n | '--deployment' | '--role' | '--task-runner' | '--community-package' | '--configuration'\n | '--approved-domain' | '--include' | '--exclude' | '--allow-unsigned-development-rules' | '--no-color';\n\nexport type V2HelpOption = Readonly<{\n name: V2OptionName;\n value: string;\n commands: readonly V2CommandName[];\n description: string;\n default?: string;\n}>;\n\nconst SCAN = Object.freeze(['scan'] as const);\nconst ALL = V2_COMMANDS;\n\nexport const V2_OPTIONS: readonly V2HelpOption[] = Object.freeze([\n { name: '--help', value: '', commands: ALL, description: 'Show deterministic help for the selected v2 command.' },\n { name: '--schema', value: '<v2>', commands: ALL, description: 'Select the versioned v2 grammar.' },\n { name: '--format', value: '<json|ndjson|sarif|table>', commands: ALL, description: 'Select the output format.', default: 'json for scan; json for metadata' },\n { name: '--profile', value: '<beta-default>', commands: SCAN, description: 'Select the admitted analysis profile.', default: 'beta-default' },\n { name: '--fail-on', value: '<CRITICAL|HIGH|MEDIUM|LOW|INFO>', commands: SCAN, description: 'Fail when a finding meets or exceeds this severity.', default: 'INFO' },\n { name: '--require-complete', value: '', commands: SCAN, description: 'Treat DEGRADED analysis as a failure.', default: 'false' },\n { name: '--max-concurrency', value: '<1..16>', commands: SCAN, description: 'Bound concurrent inputs; --concurrency is an equivalent spelling.', default: '1' },\n { name: '--concurrency', value: '<1..16>', commands: SCAN, description: 'Bound concurrent inputs; --max-concurrency is an equivalent spelling.', default: '1' },\n { name: '--timeout', value: '<1..2147483647>', commands: SCAN, description: 'Set the per-input timeout in milliseconds.', default: '30000 (workerDeadlineMs)' },\n { name: '--output', value: '<local-file|->', commands: SCAN, description: 'Write to a local file atomically, or use - for stdout.', default: 'stdout' },\n { name: '--config', value: '<local-file>', commands: SCAN, description: 'Load one explicit versioned config file.' },\n { name: '--rules', value: '<builtin>', commands: SCAN, description: 'Select the embedded built-in ruleset; the only admitted authority.', default: 'builtin' },\n { name: '--catalogue', value: '<builtin>', commands: SCAN, description: 'Select the embedded semantic catalogue.', default: 'builtin' },\n { name: '--limits', value: '<local-file|builtin>', commands: SCAN, description: 'Select embedded or local scanner limits.', default: 'builtin' },\n { name: '--target', value: '<local-file>', commands: SCAN, description: 'Load a local target context.' },\n { name: '--n8n-version', value: '<semver>', commands: SCAN, description: 'Set the n8n version fact.' },\n { name: '--edition', value: '<COMMUNITY|ENTERPRISE|CLOUD|UNKNOWN>', commands: SCAN, description: 'Set the deployment edition fact.', default: 'UNKNOWN' },\n { name: '--deployment', value: '<SELF_HOSTED|CLOUD|UNKNOWN>', commands: SCAN, description: 'Set the deployment fact.', default: 'UNKNOWN' },\n { name: '--role', value: '<OWNER|ADMIN|MEMBER|UNKNOWN>', commands: SCAN, description: 'Set the operator role fact.', default: 'UNKNOWN' },\n { name: '--task-runner', value: '<LEGACY|NATIVE|UNKNOWN>', commands: SCAN, description: 'Set the task-runner fact.', default: 'UNKNOWN' },\n { name: '--community-package', value: '<package>', commands: SCAN, description: 'Record a community package in target context; repeatable.' },\n { name: '--configuration', value: '<name>', commands: SCAN, description: 'Record a configuration fact; repeatable.' },\n { name: '--approved-domain', value: '<host>', commands: SCAN, description: 'Treat this hostname as approved for UNAPPROVED_DOMAIN; repeatable.' },\n { name: '--include', value: '<posix-glob>', commands: SCAN, description: 'Replace config includes with a case-sensitive POSIX glob; repeatable.', default: '**/*.json' },\n { name: '--exclude', value: '<posix-glob>', commands: SCAN, description: 'Replace config excludes with a case-sensitive POSIX glob; repeatable.', default: '.git/**, node_modules/**' },\n { name: '--allow-unsigned-development-rules', value: '', commands: SCAN, description: 'Refused: external rule packs are not admitted, so no pack can be selected.', default: 'false' },\n { name: '--no-color', value: '', commands: SCAN, description: 'Disable color in human output.', default: 'false (also disabled for non-TTY)' },\n]);\n\n/**\n * The published limit vocabulary, restated because a shipped declaration must not name the private,\n * unpublished contracts workspace: a strict NodeNext consumer of the packed package fails `TS2307`\n * on that import. The values below still come from the contract, and the two vocabularies stay\n * exhaustive against each other at compile time.\n */\nexport type V2LimitName =\n | 'maxBytes' | 'maxNodes' | 'maxEdges' | 'maxConnectionDepth' | 'maxParameterDepth' | 'maxParameterLeaves'\n | 'workerDeadlineMs' | 'maxSyntaxNodes' | 'maxDocumentDepth' | 'maxMembersAndElements' | 'maxRawEdges'\n | 'maxDuplicateKeys' | 'maxDiagnostics' | 'maxStoredSourceSpans' | 'maxSourceWork' | 'maxExpressionTokens'\n | 'maxCodeSyntaxNodes' | 'maxGraphOperations' | 'maxTaintStates' | 'maxTraceStepsPerFinding' | 'maxFindings'\n | 'maxRedactionPatches' | 'maxRedactedArtifactBytes' | 'maxEvidenceBytes' | 'maxSerializedResultBytes'\n | 'maxRuleOperations';\n\nexport const V2_LIMITS: Readonly<Record<V2LimitName, number>> = Object.freeze({\n maxBytes: DEFAULT_SCANNER_LIMITS_V2.maxBytes,\n maxNodes: DEFAULT_SCANNER_LIMITS_V2.maxNodes,\n maxEdges: DEFAULT_SCANNER_LIMITS_V2.maxEdges,\n maxConnectionDepth: DEFAULT_SCANNER_LIMITS_V2.maxConnectionDepth,\n maxParameterDepth: DEFAULT_SCANNER_LIMITS_V2.maxParameterDepth,\n maxParameterLeaves: DEFAULT_SCANNER_LIMITS_V2.maxParameterLeaves,\n workerDeadlineMs: DEFAULT_SCANNER_LIMITS_V2.workerDeadlineMs,\n maxSyntaxNodes: DEFAULT_SCANNER_LIMITS_V2.maxSyntaxNodes,\n maxDocumentDepth: DEFAULT_SCANNER_LIMITS_V2.maxDocumentDepth,\n maxMembersAndElements: DEFAULT_SCANNER_LIMITS_V2.maxMembersAndElements,\n maxRawEdges: DEFAULT_SCANNER_LIMITS_V2.maxRawEdges,\n maxDuplicateKeys: DEFAULT_SCANNER_LIMITS_V2.maxDuplicateKeys,\n maxDiagnostics: DEFAULT_SCANNER_LIMITS_V2.maxDiagnostics,\n maxStoredSourceSpans: DEFAULT_SCANNER_LIMITS_V2.maxStoredSourceSpans,\n maxSourceWork: DEFAULT_SCANNER_LIMITS_V2.maxSourceWork,\n maxExpressionTokens: DEFAULT_SCANNER_LIMITS_V2.maxExpressionTokens,\n maxCodeSyntaxNodes: DEFAULT_SCANNER_LIMITS_V2.maxCodeSyntaxNodes,\n maxGraphOperations: DEFAULT_SCANNER_LIMITS_V2.maxGraphOperations,\n maxTaintStates: DEFAULT_SCANNER_LIMITS_V2.maxTaintStates,\n maxTraceStepsPerFinding: DEFAULT_SCANNER_LIMITS_V2.maxTraceStepsPerFinding,\n maxFindings: DEFAULT_SCANNER_LIMITS_V2.maxFindings,\n maxRedactionPatches: DEFAULT_SCANNER_LIMITS_V2.maxRedactionPatches,\n maxRedactedArtifactBytes: DEFAULT_SCANNER_LIMITS_V2.maxRedactedArtifactBytes,\n maxEvidenceBytes: DEFAULT_SCANNER_LIMITS_V2.maxEvidenceBytes,\n maxSerializedResultBytes: DEFAULT_SCANNER_LIMITS_V2.maxSerializedResultBytes,\n maxRuleOperations: DEFAULT_SCANNER_LIMITS_V2.maxRuleOperations,\n});\n\n/**\n * Type-only parity guard: every limit the contract gains must be published here. `Exclude` is\n * `never` while the two vocabularies agree, and the type-parameter default stops satisfying its\n * constraint the moment they diverge. Erased at emit, so it costs no runtime lookup and never\n * reaches `help.d.ts`; the reverse direction is enforced by indexing `ADMITTED_LIMIT_STATES`.\n */\ntype AssertEveryContractLimitIsPublished<Gap extends never = Exclude<keyof typeof ADMITTED_LIMIT_STATES, V2LimitName>> = Gap;\n\n/** The help marker for one budget, derived from the contract authority rather than a copy. */\nfunction limitStateMarker(name: V2LimitName): string {\n const state = ADMITTED_LIMIT_STATES[name];\n if (state === 'WIRED') {\n return MEASURED_LIMIT_IDS.some((id) => id === name) ? ' (enforced; usage row)' : ' (enforced; no usage row)';\n }\n return state === 'DORMANT' ? ' (dormant in beta-default; hardened-only)' : ' (development-only, unwired)';\n}\n\nexport const V2_EXIT_CODES = Object.freeze([0, 1, 2, 3, 4, 130] as const);\nexport type V2ExitCode = (typeof V2_EXIT_CODES)[number];\nexport const V2_EXIT_REASONS = Object.freeze([\n 'CANCELLED', 'INTERNAL', 'USAGE', 'FINDING_THRESHOLD', 'REQUIRE_COMPLETE', 'ITEM_FAILURE', 'UNDER_THRESHOLD',\n] as const);\nexport type V2ExitReason = (typeof V2_EXIT_REASONS)[number];\n\nexport const V2_CANONICAL_CLAIMS = Object.freeze([\n 'v2 is offline and never initiates network requests (HTTP, DNS, sockets, telemetry, or remote imports).',\n 'Labels and output identities never contain absolute paths or workflow source content.',\n 'File output uses a same-directory temporary file and atomic replace; failures and cancellation preserve the prior file.',\n 'beta-default@2.0.0 is admitted; beta-hardened@2.0.0 is defined but not admitted or selectable.',\n 'No shipped path produces a redacted artifact: maxRedactionPatches and maxRedactedArtifactBytes bound redaction-v2, which is development-only and unwired.',\n 'No shipped path admits an external rule pack: --rules and --catalogue accept only builtin, and --allow-unsigned-development-rules is refused because Ed25519 trust-store admission is deferred.',\n 'Published on npm as sfscanner.',\n] as const);\n\nexport type V2CommandHelp = Readonly<{ command: V2CommandName; usage: readonly string[]; text: string }>;\n\nfunction optionLine(option: V2HelpOption, command?: V2CommandName): string {\n const value = option.name === '--format' && command !== undefined && command !== 'scan' ? '<json|table>' : option.value;\n const syntax = value ? ` ${value}` : '';\n const defaultText = option.default ? ` Default: ${option.default}.` : '';\n return ` ${option.name}${syntax} ${option.description}${defaultText}`;\n}\nfunction renderCommand(command: V2CommandName): V2CommandHelp {\n const usage = command === 'scan'\n ? [`sfscanner --schema v2 scan <file|directory|-> [options]`]\n : command === 'explain'\n ? [`sfscanner --schema v2 explain <rule-id> [--format json|table]`]\n : command === 'validate-config'\n ? [`sfscanner --schema v2 validate-config <local-file> [--format json|table]`]\n : [`sfscanner --schema v2 ${command} [--format json|table]`];\n const options = V2_OPTIONS.filter((option) => option.commands.includes(command));\n const lines = [`sfscanner v2 ${command}`, '', 'Usage:', ...usage, '', 'Options:', ...options.map((option) => optionLine(option, command))];\n if (command === 'scan') lines.push('', 'Inputs are sorted by normalized relative label; stdin (-) cannot join a batch.');\n if (command === 'profiles') lines.push('', 'beta-default is admitted. beta-hardened is defined but not admitted or selectable.');\n if (command === 'validate-config') lines.push('', 'Only the explicitly supplied local config file is read.');\n return Object.freeze({ command, usage: Object.freeze(usage), text: `${lines.join('\\n')}\\n` });\n}\n\nexport const COMMAND_HELP_V2: Readonly<Record<V2CommandName, string>> = Object.freeze(\n Object.fromEntries(V2_COMMANDS.map((command) => [command, renderCommand(command).text])) as Record<V2CommandName, string>,\n);\n\nconst generalLines = [\n 'sfscanner \u2014 deterministic offline n8n workflow scanner (v2)', '', 'Usage:',\n ' sfscanner --schema v2 scan <file|directory|-> [options]',\n ' sfscanner --schema v2 rules [--format json|table]',\n ' sfscanner --schema v2 explain <rule-id> [--format json|table]',\n ' sfscanner --schema v2 profiles [--format json|table]',\n ' sfscanner --schema v2 validate-config <local-file> [--format json|table]',\n ' sfscanner --schema v2 version [--format json|table]', '', 'Commands:',\n 'Defaults:', ' format: json; profile: beta-default; fail-on: INFO; require-complete: false; concurrency: 1 (maximum 16); timeout: 30000ms.',\n ' include: **/*.json; exclude: .git/**, node_modules/**; rules/catalogue/limits: builtin.',\n ' target facts: edition UNKNOWN; deployment UNKNOWN; role UNKNOWN; task-runner UNKNOWN.',\n '', 'Profiles:', ' beta-default@2.0.0: admitted and selectable.',\n ' beta-hardened@2.0.0: defined but not admitted or selectable.', '', 'Limits:',\n ...Object.entries(V2_LIMITS).map(([name, value]) => ` ${name}=${value}${limitStateMarker(name as V2LimitName)}`), '',\n 'Exit codes and precedence:', ' 130 CANCELLED; 4 INTERNAL; 2 USAGE; 1 FINDING_THRESHOLD or REQUIRE_COMPLETE; 3 ITEM_FAILURE; 0 UNDER_THRESHOLD.',\n ' Pre-scan usage/configuration/admission failures emit no machine envelope and exit 2; stdout cancellation emits a valid envelope and exits 130.',\n ' When finding threshold and require-complete both fire, FINDING_THRESHOLD wins. Indeterminate never exits 0.', '',\n ...V2_CANONICAL_CLAIMS,\n];\nexport const GENERAL_HELP_V2 = `${generalLines.join('\\n')}\\n`;\nexport const HELP_TEXT_V2 = GENERAL_HELP_V2;\n\nexport function getHelpV2(command?: V2CommandName): string {\n return command === undefined ? GENERAL_HELP_V2 : COMMAND_HELP_V2[command];\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,5 @@
1
- import type { CliMetadataDocumentV2 } from './commands';
2
- import type { CliCommandV2 } from './arguments';
1
+ import type { CliMetadataDocumentV2 } from './commands.js';
2
+ import type { CliCommandV2 } from './arguments.js';
3
3
  export type MetadataFormatV2 = 'json' | 'table';
4
4
  export type MetadataOutputOptionsV2 = Readonly<{
5
5
  color?: boolean;