@sequenceholdings/pipeline-spec 0.1.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.
- package/README.md +114 -0
- package/dist/authoring/per-table-ingestion.d.ts +74 -0
- package/dist/authoring/per-table-ingestion.d.ts.map +1 -0
- package/dist/authoring/per-table-ingestion.js +122 -0
- package/dist/authoring/per-table-ingestion.js.map +1 -0
- package/dist/authoring/yaml.d.ts +6 -0
- package/dist/authoring/yaml.d.ts.map +1 -0
- package/dist/authoring/yaml.js +15 -0
- package/dist/authoring/yaml.js.map +1 -0
- package/dist/binding-gaps.d.ts +21 -0
- package/dist/binding-gaps.d.ts.map +1 -0
- package/dist/binding-gaps.js +74 -0
- package/dist/binding-gaps.js.map +1 -0
- package/dist/bodies/ingestion.d.ts +359 -0
- package/dist/bodies/ingestion.d.ts.map +1 -0
- package/dist/bodies/ingestion.js +446 -0
- package/dist/bodies/ingestion.js.map +1 -0
- package/dist/bodies/serving.d.ts +316 -0
- package/dist/bodies/serving.d.ts.map +1 -0
- package/dist/bodies/serving.js +290 -0
- package/dist/bodies/serving.js.map +1 -0
- package/dist/bodies/transformation.d.ts +397 -0
- package/dist/bodies/transformation.d.ts.map +1 -0
- package/dist/bodies/transformation.js +278 -0
- package/dist/bodies/transformation.js.map +1 -0
- package/dist/catalog-binding.d.ts +15 -0
- package/dist/catalog-binding.d.ts.map +1 -0
- package/dist/catalog-binding.js +15 -0
- package/dist/catalog-binding.js.map +1 -0
- package/dist/columns.d.ts +177 -0
- package/dist/columns.d.ts.map +1 -0
- package/dist/columns.js +89 -0
- package/dist/columns.js.map +1 -0
- package/dist/compatibility.d.ts +25 -0
- package/dist/compatibility.d.ts.map +1 -0
- package/dist/compatibility.js +108 -0
- package/dist/compatibility.js.map +1 -0
- package/dist/constants.d.ts +95 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +105 -0
- package/dist/constants.js.map +1 -0
- package/dist/diff.d.ts +36 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +300 -0
- package/dist/diff.js.map +1 -0
- package/dist/documentation.d.ts +22 -0
- package/dist/documentation.d.ts.map +1 -0
- package/dist/documentation.js +43 -0
- package/dist/documentation.js.map +1 -0
- package/dist/envelope.d.ts +1241 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +163 -0
- package/dist/envelope.js.map +1 -0
- package/dist/errors.d.ts +30 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +47 -0
- package/dist/errors.js.map +1 -0
- package/dist/graph-references.d.ts +34 -0
- package/dist/graph-references.d.ts.map +1 -0
- package/dist/graph-references.js +81 -0
- package/dist/graph-references.js.map +1 -0
- package/dist/graph.d.ts +112 -0
- package/dist/graph.d.ts.map +1 -0
- package/dist/graph.js +546 -0
- package/dist/graph.js.map +1 -0
- package/dist/hash.d.ts +28 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +91 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/issues.d.ts +14 -0
- package/dist/issues.d.ts.map +1 -0
- package/dist/issues.js +27 -0
- package/dist/issues.js.map +1 -0
- package/dist/loader.d.ts +50 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +241 -0
- package/dist/loader.js.map +1 -0
- package/dist/migrations/contracts.d.ts +38 -0
- package/dist/migrations/contracts.d.ts.map +1 -0
- package/dist/migrations/contracts.js +175 -0
- package/dist/migrations/contracts.js.map +1 -0
- package/dist/migrations/manifest.d.ts +137 -0
- package/dist/migrations/manifest.d.ts.map +1 -0
- package/dist/migrations/manifest.js +137 -0
- package/dist/migrations/manifest.js.map +1 -0
- package/dist/operations.d.ts +214 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +275 -0
- package/dist/operations.js.map +1 -0
- package/dist/orm-satisfies.d.ts +132 -0
- package/dist/orm-satisfies.d.ts.map +1 -0
- package/dist/orm-satisfies.js +119 -0
- package/dist/orm-satisfies.js.map +1 -0
- package/dist/primitives.d.ts +11 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +20 -0
- package/dist/primitives.js.map +1 -0
- package/dist/resolve.d.ts +124 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +194 -0
- package/dist/resolve.js.map +1 -0
- package/dist/secret-scan.d.ts +34 -0
- package/dist/secret-scan.d.ts.map +1 -0
- package/dist/secret-scan.js +98 -0
- package/dist/secret-scan.js.map +1 -0
- package/dist/serving-compatibility.d.ts +14 -0
- package/dist/serving-compatibility.d.ts.map +1 -0
- package/dist/serving-compatibility.js +19 -0
- package/dist/serving-compatibility.js.map +1 -0
- package/dist/snapshot-transport.d.ts +83 -0
- package/dist/snapshot-transport.d.ts.map +1 -0
- package/dist/snapshot-transport.js +42 -0
- package/dist/snapshot-transport.js.map +1 -0
- package/dist/suggestions.d.ts +3 -0
- package/dist/suggestions.d.ts.map +1 -0
- package/dist/suggestions.js +26 -0
- package/dist/suggestions.js.map +1 -0
- package/dist/types.d.ts +72 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +119 -0
- package/dist/types.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serving `satisfies:` enforcement against an ORM synced-table contract
|
|
3
|
+
* (SEQ-2499). Pure — callers (offline `validateSpecGraph`, provision plan)
|
|
4
|
+
* supply the active contract catalog; this module never fetches ORM state.
|
|
5
|
+
*
|
|
6
|
+
* Compatibility posture:
|
|
7
|
+
* - Projection is the **producer**; ORM contract columns are the **consumer
|
|
8
|
+
* requirement** (`checkCompatibility`).
|
|
9
|
+
* - Extra projection columns are allowed (additive pipeline supersets).
|
|
10
|
+
* - Primary keys must match exactly (same set + order).
|
|
11
|
+
* - `content_hash` on `satisfies` must equal the active contract hash.
|
|
12
|
+
*/
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { checkCompatibility } from './compatibility.js';
|
|
15
|
+
import { columnIdentifierSchema, columnMapSchema, identifierSchema } from './columns.js';
|
|
16
|
+
export const ORM_SATISFIES_FINDING_CODES = [
|
|
17
|
+
'orm_contract_unavailable',
|
|
18
|
+
'orm_contract_hash_mismatch',
|
|
19
|
+
'orm_contract_pk_mismatch',
|
|
20
|
+
'missing_column',
|
|
21
|
+
'type_incompatible',
|
|
22
|
+
'nullability_violation',
|
|
23
|
+
'domain_violation',
|
|
24
|
+
'domain_shrunk',
|
|
25
|
+
];
|
|
26
|
+
/** One ORM synced-table contract entry for offline `satisfies` validation. */
|
|
27
|
+
export const ormSyncedContractRequirementSchema = z.strictObject({
|
|
28
|
+
namespace: z
|
|
29
|
+
.string()
|
|
30
|
+
.regex(/^[a-z][a-z0-9_]{0,40}$/, 'must be an ORM namespace (lowercase, ≤41 chars)'),
|
|
31
|
+
table: identifierSchema,
|
|
32
|
+
columns: columnMapSchema,
|
|
33
|
+
primary_key: z.array(columnIdentifierSchema).min(1),
|
|
34
|
+
content_hash: z
|
|
35
|
+
.string()
|
|
36
|
+
.regex(/^[a-f0-9]{64}$/, 'must be a sha256 hex digest (64 hex chars)'),
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* Offline ORM contract catalog for `seq-studio pipeline validate --orm-contracts`
|
|
40
|
+
* or `<pipeline-dir>/orm-contracts.json`. Keys must be `namespace.table`.
|
|
41
|
+
*/
|
|
42
|
+
export const ormContractCatalogSchema = z
|
|
43
|
+
.record(z.string().min(1), ormSyncedContractRequirementSchema)
|
|
44
|
+
.superRefine((catalog, ctx) => {
|
|
45
|
+
for (const [key, value] of Object.entries(catalog)) {
|
|
46
|
+
const expected = `${value.namespace}.${value.table}`;
|
|
47
|
+
if (key !== expected) {
|
|
48
|
+
ctx.addIssue({
|
|
49
|
+
code: z.ZodIssueCode.custom,
|
|
50
|
+
path: [key],
|
|
51
|
+
message: `catalog key '${key}' must equal '${expected}' (namespace.table)`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
export function ormContractCatalogKey({ namespace, table, }) {
|
|
57
|
+
return `${namespace}.${table}`;
|
|
58
|
+
}
|
|
59
|
+
function primaryKeysEqual(left, right) {
|
|
60
|
+
if (left.length !== right.length)
|
|
61
|
+
return false;
|
|
62
|
+
return left.every((value, index) => value === right[index]);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Does this serving projection satisfy the declared ORM contract binding?
|
|
66
|
+
* Returns findings (empty = ok). Hash + PK mismatches are always errors;
|
|
67
|
+
* column findings reuse `checkCompatibility` severities.
|
|
68
|
+
*/
|
|
69
|
+
export function checkServingSatisfiesOrmContract({ projection, satisfies, contract, }) {
|
|
70
|
+
const target = `${satisfies.namespace}.${satisfies.table}`;
|
|
71
|
+
if (!contract) {
|
|
72
|
+
return [
|
|
73
|
+
{
|
|
74
|
+
code: 'orm_contract_unavailable',
|
|
75
|
+
severity: 'error',
|
|
76
|
+
message: `satisfies references ORM contract '${target}' but no active synced-table contract was provided to validation`,
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
if (contract.namespace !== satisfies.namespace || contract.table !== satisfies.table) {
|
|
81
|
+
return [
|
|
82
|
+
{
|
|
83
|
+
code: 'orm_contract_unavailable',
|
|
84
|
+
severity: 'error',
|
|
85
|
+
message: `satisfies references '${target}' but the supplied contract is '${contract.namespace}.${contract.table}'`,
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
}
|
|
89
|
+
const findings = [];
|
|
90
|
+
if (contract.content_hash !== satisfies.content_hash) {
|
|
91
|
+
findings.push({
|
|
92
|
+
code: 'orm_contract_hash_mismatch',
|
|
93
|
+
severity: 'error',
|
|
94
|
+
message: `satisfies.content_hash does not match the active ORM contract for '${target}' ` +
|
|
95
|
+
`(declared '${satisfies.content_hash.slice(0, 12)}…', active '${contract.content_hash.slice(0, 12)}…')`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (!primaryKeysEqual(projection.primary_key, contract.primary_key)) {
|
|
99
|
+
findings.push({
|
|
100
|
+
code: 'orm_contract_pk_mismatch',
|
|
101
|
+
severity: 'error',
|
|
102
|
+
message: `projection.primary_key [${projection.primary_key.join(', ')}] must exactly match ` +
|
|
103
|
+
`ORM contract primary_key [${contract.primary_key.join(', ')}] for '${target}'`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
for (const finding of checkCompatibility(projection.columns, contract.columns)) {
|
|
107
|
+
findings.push(toOrmSatisfiesFinding(finding, target));
|
|
108
|
+
}
|
|
109
|
+
return findings;
|
|
110
|
+
}
|
|
111
|
+
function toOrmSatisfiesFinding(finding, target) {
|
|
112
|
+
return {
|
|
113
|
+
code: finding.code,
|
|
114
|
+
severity: finding.severity,
|
|
115
|
+
column: finding.column,
|
|
116
|
+
message: `ORM contract '${target}': ${finding.message}`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=orm-satisfies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orm-satisfies.js","sourceRoot":"","sources":["../src/orm-satisfies.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Grammar primitives shared across the envelope, bodies, and operations —
|
|
4
|
+
* kept dependency-free (constants only) so any schema module can import them
|
|
5
|
+
* without cycles.
|
|
6
|
+
*/
|
|
7
|
+
export declare const isoDurationSchema: z.ZodString;
|
|
8
|
+
export declare const symbolicRefSchema: z.ZodString;
|
|
9
|
+
/** Repo-relative code path (entrypoints, decoders). Never absolute, never escaping the repo. */
|
|
10
|
+
export declare const repoRelativePathSchema: z.ZodString;
|
|
11
|
+
//# sourceMappingURL=primitives.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../src/primitives.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB;;;;GAIG;AAEH,eAAO,MAAM,iBAAiB,aAEiD,CAAA;AAE/E,eAAO,MAAM,iBAAiB,aAE8E,CAAA;AAE5G,gGAAgG;AAChG,eAAO,MAAM,sBAAsB,aAI0C,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ISO_DURATION_PATTERN, SYMBOLIC_REF_PATTERN } from './constants.js';
|
|
3
|
+
/**
|
|
4
|
+
* Grammar primitives shared across the envelope, bodies, and operations —
|
|
5
|
+
* kept dependency-free (constants only) so any schema module can import them
|
|
6
|
+
* without cycles.
|
|
7
|
+
*/
|
|
8
|
+
export const isoDurationSchema = z
|
|
9
|
+
.string()
|
|
10
|
+
.regex(ISO_DURATION_PATTERN, 'must be an ISO-8601 duration (e.g. PT4H, P1D)');
|
|
11
|
+
export const symbolicRefSchema = z
|
|
12
|
+
.string()
|
|
13
|
+
.regex(SYMBOLIC_REF_PATTERN, 'must be a symbolic reference slug (a-z, 0-9, -, _) — never a literal value');
|
|
14
|
+
/** Repo-relative code path (entrypoints, decoders). Never absolute, never escaping the repo. */
|
|
15
|
+
export const repoRelativePathSchema = z
|
|
16
|
+
.string()
|
|
17
|
+
.min(1)
|
|
18
|
+
.refine((value) => !value.startsWith('/') && !value.startsWith('.'), 'must be a repo-relative path')
|
|
19
|
+
.refine((value) => !value.split('/').includes('..'), 'must not contain ..');
|
|
20
|
+
//# sourceMappingURL=primitives.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"primitives.js","sourceRoot":"","sources":["../src/primitives.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAE3E;;;;GAIG;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC;KAC/B,MAAM,EAAE;KACR,KAAK,CAAC,oBAAoB,EAAE,+CAA+C,CAAC,CAAA;AAE/E,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC;KAC/B,MAAM,EAAE;KACR,KAAK,CAAC,oBAAoB,EAAE,4EAA4E,CAAC,CAAA;AAE5G,gGAAgG;AAChG,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC;KACpC,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,CAAC;KACN,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,8BAA8B,CAAC;KACnG,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,qBAAqB,CAAC,CAAA"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type PostgresSnapshotBinding, type SnapshotTransport } from './snapshot-transport.js';
|
|
3
|
+
import { type StageSpec } from './envelope.js';
|
|
4
|
+
/**
|
|
5
|
+
* The facts a deploy target contributes to resolution. Supplied by the
|
|
6
|
+
* caller, never discovered by the SDK.
|
|
7
|
+
*
|
|
8
|
+
* **One-workspace-per-deployment invariant:** each deploy target binds
|
|
9
|
+
* exactly one `workspace_id`. The field is schema-optional ONLY so that
|
|
10
|
+
* resolution can fail with a structured, enumerable error
|
|
11
|
+
* (`missing_workspace_binding`) instead of a generic parse error — there is
|
|
12
|
+
* no global-host fallback. (Today's shared first-party workspace is a
|
|
13
|
+
* transitional N:1 fact; the model assumes the target 1:1 coupling.)
|
|
14
|
+
*/
|
|
15
|
+
export declare const targetFactsSchema: z.ZodObject<{
|
|
16
|
+
deploy_target: z.ZodString;
|
|
17
|
+
tenant_id: z.ZodOptional<z.ZodString>;
|
|
18
|
+
workspace_id: z.ZodOptional<z.ZodString>;
|
|
19
|
+
catalog: z.ZodString;
|
|
20
|
+
lakebase_branch: z.ZodOptional<z.ZodString>;
|
|
21
|
+
postgres_snapshot_bindings: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
22
|
+
bindingId: z.ZodString;
|
|
23
|
+
database: z.ZodString;
|
|
24
|
+
bindingHash: z.ZodString;
|
|
25
|
+
installationId: z.ZodString;
|
|
26
|
+
}, z.core.$strict>>>;
|
|
27
|
+
alert_channel_bindings: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
28
|
+
credential_bindings: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
29
|
+
}, z.core.$strict>;
|
|
30
|
+
export type TargetFacts = z.infer<typeof targetFactsSchema>;
|
|
31
|
+
export type TargetFactsInput = z.input<typeof targetFactsSchema>;
|
|
32
|
+
/** Where the resolved snapshot came from — stamped by the caller (deploy adapter / CI). */
|
|
33
|
+
export interface SpecProvenanceInput {
|
|
34
|
+
readonly source_repo: string;
|
|
35
|
+
readonly source_commit: string;
|
|
36
|
+
}
|
|
37
|
+
export interface ResolvedProvenance extends SpecProvenanceInput {
|
|
38
|
+
readonly spec_hash: string;
|
|
39
|
+
readonly contract_hash: string;
|
|
40
|
+
}
|
|
41
|
+
/** Identity facts stamped onto every resolved snapshot. */
|
|
42
|
+
export interface ResolvedIdentity {
|
|
43
|
+
readonly deploy_target: string;
|
|
44
|
+
readonly tenant_id?: string;
|
|
45
|
+
readonly workspace_id: string;
|
|
46
|
+
readonly catalog: string;
|
|
47
|
+
readonly lakebase_branch?: string;
|
|
48
|
+
}
|
|
49
|
+
/** Concrete bindings for the spec's symbolic references. */
|
|
50
|
+
export interface ResolvedBindings {
|
|
51
|
+
readonly credentials: Readonly<Record<string, string>>;
|
|
52
|
+
readonly alert_channels: Readonly<Record<string, string>>;
|
|
53
|
+
}
|
|
54
|
+
export interface ResolvedFeedNames {
|
|
55
|
+
/**
|
|
56
|
+
* Volume batch-directory prefix: `/Volumes/src_<source>/bronze/<feed>`
|
|
57
|
+
* (batches land under `batch_id=<ts>/`). Rooted in the per-source catalog,
|
|
58
|
+
* NOT the deployment catalog.
|
|
59
|
+
*/
|
|
60
|
+
readonly volume_path: string;
|
|
61
|
+
/** Platform-derived append-only bronze table FQN: `src_<source>.bronze.<feed>`. */
|
|
62
|
+
readonly bronze_table: string;
|
|
63
|
+
}
|
|
64
|
+
export interface ResolvedIngestionNames {
|
|
65
|
+
readonly kind: 'ingestion';
|
|
66
|
+
readonly feeds: Readonly<Record<string, ResolvedFeedNames>>;
|
|
67
|
+
}
|
|
68
|
+
export interface ResolvedOutputNames {
|
|
69
|
+
/**
|
|
70
|
+
* Physical FQN of the output relation. Defaults to the deploy catalog;
|
|
71
|
+
* `config.catalog` pins a parallel UC namespace (e.g. `src_banksouth_fis`).
|
|
72
|
+
*/
|
|
73
|
+
readonly table: string;
|
|
74
|
+
/** Physical FQN of the quarantine sibling — present iff any column quarantines. */
|
|
75
|
+
readonly quarantine_table?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* UC catalog a transformation writes. `config.catalog` pins a parallel
|
|
79
|
+
* source catalog; `identity.catalog` stays the deploy target.
|
|
80
|
+
*/
|
|
81
|
+
export declare function transformationOutputCatalog(spec: Extract<StageSpec, {
|
|
82
|
+
type: 'transformation';
|
|
83
|
+
}>, deployCatalog: string, deployTarget?: string): string;
|
|
84
|
+
export interface ResolvedTransformationNames {
|
|
85
|
+
readonly kind: 'transformation';
|
|
86
|
+
readonly outputs: Readonly<Record<string, ResolvedOutputNames>>;
|
|
87
|
+
}
|
|
88
|
+
export interface ResolvedServingNames {
|
|
89
|
+
readonly transport?: {
|
|
90
|
+
readonly kind: 'native_lakebase_sync_v1';
|
|
91
|
+
} | (PostgresSnapshotBinding & Omit<SnapshotTransport, 'binding'>);
|
|
92
|
+
readonly kind: 'serving';
|
|
93
|
+
/** FQN of the UC source relation the sync reads. */
|
|
94
|
+
readonly source_table: string;
|
|
95
|
+
/**
|
|
96
|
+
* The declared public view. The Postgres physical relation is deliberately
|
|
97
|
+
* ABSENT: physical names are captured at provision time and stored in the
|
|
98
|
+
* registry, never re-derived (the estate has documented derivation drift).
|
|
99
|
+
*/
|
|
100
|
+
readonly view: string;
|
|
101
|
+
/** Optional Lakebase reporting-schema compatibility relation. */
|
|
102
|
+
readonly reporting_table?: string;
|
|
103
|
+
}
|
|
104
|
+
export type ResolvedNames = ResolvedIngestionNames | ResolvedTransformationNames | ResolvedServingNames;
|
|
105
|
+
/**
|
|
106
|
+
* The immutable resolved snapshot: the portable spec, the identity it was
|
|
107
|
+
* resolved against, concrete bindings, derived physical names, and hashed
|
|
108
|
+
* provenance. Serialize with `canonicalJson` for storage — byte-stable.
|
|
109
|
+
*/
|
|
110
|
+
export interface ResolvedSpec {
|
|
111
|
+
readonly spec: StageSpec;
|
|
112
|
+
readonly identity: ResolvedIdentity;
|
|
113
|
+
readonly bindings: ResolvedBindings;
|
|
114
|
+
readonly derived: ResolvedNames;
|
|
115
|
+
readonly provenance: ResolvedProvenance;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Resolve a portable spec against a deploy target's facts. Pure — binds
|
|
119
|
+
* symbolic references, derives physical names per the body derivation rules,
|
|
120
|
+
* stamps identity + hashed provenance. Throws `SpecResolutionError` with an
|
|
121
|
+
* enumerable code on any unresolvable fact.
|
|
122
|
+
*/
|
|
123
|
+
export declare function resolveSpec(spec: StageSpec, facts: TargetFactsInput, provenance: SpecProvenanceInput): ResolvedSpec;
|
|
124
|
+
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"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"}
|
package/dist/resolve.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { postgresSnapshotBindingsSchema } from './snapshot-transport.js';
|
|
3
|
+
import { resolveCatalogBinding } from './catalog-binding.js';
|
|
4
|
+
import { quarantineAssetName } from './constants.js';
|
|
5
|
+
import { requireResolvedColumns } from './columns.js';
|
|
6
|
+
import { SpecResolutionError } from './errors.js';
|
|
7
|
+
import { deployTargetSchema } from './envelope.js';
|
|
8
|
+
import { feedBronzeTable, feedVolumePath, ingestionSourceIdentity, symbolicRefSchema, } from './bodies/ingestion.js';
|
|
9
|
+
import { computeContractHash, computeSpecHash } from './hash.js';
|
|
10
|
+
import { collectBindingGaps } from './binding-gaps.js';
|
|
11
|
+
import { assertFeedDocumentationResolved } from './documentation.js';
|
|
12
|
+
/**
|
|
13
|
+
* A portable spec resolved against a deploy target's facts produces the
|
|
14
|
+
* immutable **resolved snapshot** that deployments record and telemetry
|
|
15
|
+
* references. `resolveSpec` is pure — no I/O, no discovery: the caller
|
|
16
|
+
* (deploy adapter / CLI) supplies the facts.
|
|
17
|
+
*/
|
|
18
|
+
const nonEmpty = z.string().min(1);
|
|
19
|
+
/** Numeric Databricks workspace id (`DATABRICKS_ORG_ID`), not the `dbc-…` host prefix. */
|
|
20
|
+
const workspaceIdSchema = nonEmpty.regex(/^\d+$/, 'workspace_id must be the numeric Databricks workspace id (DATABRICKS_ORG_ID)');
|
|
21
|
+
/**
|
|
22
|
+
* The facts a deploy target contributes to resolution. Supplied by the
|
|
23
|
+
* caller, never discovered by the SDK.
|
|
24
|
+
*
|
|
25
|
+
* **One-workspace-per-deployment invariant:** each deploy target binds
|
|
26
|
+
* exactly one `workspace_id`. The field is schema-optional ONLY so that
|
|
27
|
+
* resolution can fail with a structured, enumerable error
|
|
28
|
+
* (`missing_workspace_binding`) instead of a generic parse error — there is
|
|
29
|
+
* no global-host fallback. (Today's shared first-party workspace is a
|
|
30
|
+
* transitional N:1 fact; the model assumes the target 1:1 coupling.)
|
|
31
|
+
*/
|
|
32
|
+
export const targetFactsSchema = z.strictObject({
|
|
33
|
+
deploy_target: deployTargetSchema,
|
|
34
|
+
tenant_id: nonEmpty.optional(),
|
|
35
|
+
workspace_id: workspaceIdSchema.optional(),
|
|
36
|
+
/** UC catalog physical names are derived into (e.g. `banksouth`, `staging`). */
|
|
37
|
+
catalog: nonEmpty,
|
|
38
|
+
lakebase_branch: nonEmpty.optional(),
|
|
39
|
+
postgres_snapshot_bindings: postgresSnapshotBindingsSchema.optional(),
|
|
40
|
+
/** Symbolic alert channel → concrete route (Slack channel id, pager key). */
|
|
41
|
+
alert_channel_bindings: z.record(symbolicRefSchema, nonEmpty).default({}),
|
|
42
|
+
/**
|
|
43
|
+
* Databricks source credential family → binding id (secret-manager
|
|
44
|
+
* reference, never a secret value). Edge-worker Data Sync credential
|
|
45
|
+
* families stay on the worker binding and are validated by the Data Sync
|
|
46
|
+
* operation that runs on the registered machine.
|
|
47
|
+
*/
|
|
48
|
+
credential_bindings: z.record(symbolicRefSchema, nonEmpty).default({}),
|
|
49
|
+
});
|
|
50
|
+
/**
|
|
51
|
+
* UC catalog a transformation writes. `config.catalog` pins a parallel
|
|
52
|
+
* source catalog; `identity.catalog` stays the deploy target.
|
|
53
|
+
*/
|
|
54
|
+
export function transformationOutputCatalog(spec, deployCatalog, deployTarget) {
|
|
55
|
+
return resolveCatalogBinding({
|
|
56
|
+
binding: spec.config?.catalog,
|
|
57
|
+
deployTarget,
|
|
58
|
+
}) ?? deployCatalog;
|
|
59
|
+
}
|
|
60
|
+
function requireCredential(facts, symbolic) {
|
|
61
|
+
const binding = facts.credential_bindings[symbolic];
|
|
62
|
+
if (!binding) {
|
|
63
|
+
throw new SpecResolutionError('missing_credential_binding', `target '${facts.deploy_target}' has no credential binding for symbolic name '${symbolic}'`, symbolic);
|
|
64
|
+
}
|
|
65
|
+
return binding;
|
|
66
|
+
}
|
|
67
|
+
function resolveBindings(spec, facts) {
|
|
68
|
+
const credentials = {};
|
|
69
|
+
if (spec.type === 'ingestion') {
|
|
70
|
+
// Edge-worker credentials name the Data Sync environment family used by
|
|
71
|
+
// the registered machine (for example, fis_ibm -> FIS_IBM). The VM owns
|
|
72
|
+
// those credentials, so they must not be resolved through the Databricks
|
|
73
|
+
// managed-secret binding map.
|
|
74
|
+
//
|
|
75
|
+
// Keyless/public databricks sources omit `source.credential` — only
|
|
76
|
+
// resolve when a symbolic family is declared (never invent a sentinel
|
|
77
|
+
// binding).
|
|
78
|
+
if (spec.source?.credential) {
|
|
79
|
+
credentials[spec.source.credential] = requireCredential(facts, spec.source.credential);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const alertChannels = {};
|
|
83
|
+
for (const channel of spec.alerts?.channels ?? []) {
|
|
84
|
+
const route = facts.alert_channel_bindings[channel];
|
|
85
|
+
if (!route) {
|
|
86
|
+
throw new SpecResolutionError('missing_alert_channel_binding', `target '${facts.deploy_target}' has no alert channel binding for symbolic name '${channel}'`, channel);
|
|
87
|
+
}
|
|
88
|
+
alertChannels[channel] = route;
|
|
89
|
+
}
|
|
90
|
+
return { credentials, alert_channels: alertChannels };
|
|
91
|
+
}
|
|
92
|
+
function resolveServingTransport({ transport, facts }) {
|
|
93
|
+
if (transport.kind === 'native_lakebase_sync_v1')
|
|
94
|
+
return transport;
|
|
95
|
+
const binding = facts.postgres_snapshot_bindings?.[transport.binding];
|
|
96
|
+
if (!binding || binding.database !== transport.database)
|
|
97
|
+
throw new SpecResolutionError('invalid_target_facts', 'snapshot transport binding missing or mismatched');
|
|
98
|
+
const { binding: _alias, ...target } = transport;
|
|
99
|
+
return { ...target, ...binding };
|
|
100
|
+
}
|
|
101
|
+
function deriveNames(spec, facts) {
|
|
102
|
+
const catalog = facts.catalog;
|
|
103
|
+
switch (spec.type) {
|
|
104
|
+
case 'ingestion': {
|
|
105
|
+
// Ingestion names derive from the spec's OWN source identity into the
|
|
106
|
+
// per-source catalog (`src_<source>`) — never from the deployment
|
|
107
|
+
// catalog, which owns silver/gold/report/serving only. Canonical derivations
|
|
108
|
+
// live beside the body schema so docs, resolver, and tests can't drift.
|
|
109
|
+
const source = ingestionSourceIdentity(spec);
|
|
110
|
+
const feeds = Object.fromEntries(Object.keys(spec.feeds).map((feed) => [
|
|
111
|
+
feed,
|
|
112
|
+
{
|
|
113
|
+
volume_path: feedVolumePath({ source, feed }),
|
|
114
|
+
bronze_table: feedBronzeTable({ source, feed }),
|
|
115
|
+
},
|
|
116
|
+
]));
|
|
117
|
+
return { kind: 'ingestion', feeds };
|
|
118
|
+
}
|
|
119
|
+
case 'transformation': {
|
|
120
|
+
const outputCatalog = transformationOutputCatalog(spec, catalog, facts.deploy_target);
|
|
121
|
+
const outputs = Object.fromEntries(Object.entries(spec.outputs).map(([asset, output]) => {
|
|
122
|
+
const table = `${outputCatalog}.${asset}`;
|
|
123
|
+
// Quarantine rows come from EITHER level (SEQ-2437/SEQ-2439): the
|
|
124
|
+
// output-level type-violation posture, or any single column's
|
|
125
|
+
// on_violation — both land in the same platform-derived sibling.
|
|
126
|
+
const columns = requireResolvedColumns(output.columns, `${spec.stage}: outputs.${asset}.columns`);
|
|
127
|
+
const quarantines = output.on_type_violation === 'quarantine' ||
|
|
128
|
+
Object.values(columns).some((column) => column.on_violation === 'quarantine');
|
|
129
|
+
return [
|
|
130
|
+
asset,
|
|
131
|
+
quarantines
|
|
132
|
+
? { table, quarantine_table: `${outputCatalog}.${quarantineAssetName(asset)}` }
|
|
133
|
+
: { table },
|
|
134
|
+
];
|
|
135
|
+
}));
|
|
136
|
+
return { kind: 'transformation', outputs };
|
|
137
|
+
}
|
|
138
|
+
case 'serving':
|
|
139
|
+
return {
|
|
140
|
+
kind: 'serving',
|
|
141
|
+
...(spec.transport ? { transport: resolveServingTransport({ transport: spec.transport, facts }) } : {}),
|
|
142
|
+
// Optional `source.catalog` binds a parallel producer namespace
|
|
143
|
+
// (e.g. `src_banksouth_fis`); the synced-table destination still uses
|
|
144
|
+
// `facts.catalog`.
|
|
145
|
+
source_table: `${resolveCatalogBinding({
|
|
146
|
+
binding: spec.source.catalog,
|
|
147
|
+
deployTarget: facts.deploy_target,
|
|
148
|
+
}) ?? catalog}.${spec.source.output}`,
|
|
149
|
+
view: spec.expose.view,
|
|
150
|
+
...(spec.expose.reporting_table
|
|
151
|
+
? { reporting_table: spec.expose.reporting_table }
|
|
152
|
+
: {}),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Resolve a portable spec against a deploy target's facts. Pure — binds
|
|
158
|
+
* symbolic references, derives physical names per the body derivation rules,
|
|
159
|
+
* stamps identity + hashed provenance. Throws `SpecResolutionError` with an
|
|
160
|
+
* enumerable code on any unresolvable fact.
|
|
161
|
+
*/
|
|
162
|
+
export function resolveSpec(spec, facts, provenance) {
|
|
163
|
+
assertFeedDocumentationResolved(spec);
|
|
164
|
+
const gaps = collectBindingGaps(spec, facts);
|
|
165
|
+
const gap = gaps[0];
|
|
166
|
+
if (gap) {
|
|
167
|
+
throw new SpecResolutionError(gap.code, gap.message, gap.symbolic);
|
|
168
|
+
}
|
|
169
|
+
const target = targetFactsSchema.parse(facts);
|
|
170
|
+
const workspaceId = target.workspace_id;
|
|
171
|
+
if (workspaceId === undefined) {
|
|
172
|
+
throw new SpecResolutionError('missing_workspace_binding', `target '${target.deploy_target}' has no workspace binding — every deploy target must bind exactly one workspace_id (no fallback)`, target.deploy_target);
|
|
173
|
+
}
|
|
174
|
+
const identity = {
|
|
175
|
+
deploy_target: target.deploy_target,
|
|
176
|
+
workspace_id: workspaceId,
|
|
177
|
+
catalog: target.catalog,
|
|
178
|
+
...(target.tenant_id !== undefined ? { tenant_id: target.tenant_id } : {}),
|
|
179
|
+
...(target.lakebase_branch !== undefined ? { lakebase_branch: target.lakebase_branch } : {}),
|
|
180
|
+
};
|
|
181
|
+
return {
|
|
182
|
+
spec,
|
|
183
|
+
identity,
|
|
184
|
+
bindings: resolveBindings(spec, target),
|
|
185
|
+
derived: deriveNames(spec, target),
|
|
186
|
+
provenance: {
|
|
187
|
+
source_repo: provenance.source_repo,
|
|
188
|
+
source_commit: provenance.source_commit,
|
|
189
|
+
spec_hash: computeSpecHash(spec),
|
|
190
|
+
contract_hash: computeContractHash(spec),
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Specs carry symbolic references only — credential families, alert channels,
|
|
3
|
+
* integrations. This scan walks every string leaf of a parsed spec and rejects
|
|
4
|
+
* anything that looks like a literal secret, credential, connection string, or
|
|
5
|
+
* webhook URL, so a copy-pasted token can never ride into the registry.
|
|
6
|
+
*
|
|
7
|
+
* It is a tripwire, not a scanner-of-record: the tight identifier grammars on
|
|
8
|
+
* symbolic fields do most of the work; this catches the free-text fields
|
|
9
|
+
* (descriptions, extraction params, runbook) where a literal could hide.
|
|
10
|
+
*/
|
|
11
|
+
export interface CredentialFinding {
|
|
12
|
+
/** Dotted path to the offending string leaf (e.g. `feeds.games.extraction.conn`) — display only. */
|
|
13
|
+
readonly path: string;
|
|
14
|
+
/**
|
|
15
|
+
* Exact path segments to the leaf. Use THESE for Zod issue paths — keys may
|
|
16
|
+
* themselves contain dots (`outputs['gold.players']`), so splitting the
|
|
17
|
+
* dotted string would point at the wrong location.
|
|
18
|
+
*/
|
|
19
|
+
readonly segments: readonly string[];
|
|
20
|
+
readonly reason: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Reason the string looks like a credential/webhook literal, or null when it
|
|
24
|
+
* is clean. `urlAllowed` permits a plain https URL (for `runbook`) while still
|
|
25
|
+
* rejecting webhook hosts and embedded credentials.
|
|
26
|
+
*/
|
|
27
|
+
export declare function credentialLikeReason(value: string, urlAllowed?: boolean): string | null;
|
|
28
|
+
/**
|
|
29
|
+
* Walk every string leaf of a parsed spec (post-YAML object tree) and collect
|
|
30
|
+
* credential-looking values. Object keys are grammar-constrained by the
|
|
31
|
+
* schemas, so only values are scanned.
|
|
32
|
+
*/
|
|
33
|
+
export declare function scanForCredentialLikeStrings(root: unknown): CredentialFinding[];
|
|
34
|
+
//# sourceMappingURL=secret-scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-scan.d.ts","sourceRoot":"","sources":["../src/secret-scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,iBAAiB;IAChC,oGAAoG;IACpG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AA6BD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,UAAQ,GAAG,MAAM,GAAG,IAAI,CAyBrF;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,OAAO,GAAG,iBAAiB,EAAE,CAI/E"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Specs carry symbolic references only — credential families, alert channels,
|
|
3
|
+
* integrations. This scan walks every string leaf of a parsed spec and rejects
|
|
4
|
+
* anything that looks like a literal secret, credential, connection string, or
|
|
5
|
+
* webhook URL, so a copy-pasted token can never ride into the registry.
|
|
6
|
+
*
|
|
7
|
+
* It is a tripwire, not a scanner-of-record: the tight identifier grammars on
|
|
8
|
+
* symbolic fields do most of the work; this catches the free-text fields
|
|
9
|
+
* (descriptions, extraction params, runbook) where a literal could hide.
|
|
10
|
+
*/
|
|
11
|
+
/** Fields that legitimately hold an https URL (still scanned for webhook/credential shapes). */
|
|
12
|
+
const URL_ALLOWED_PATHS = new Set(['runbook']);
|
|
13
|
+
const WEBHOOK_HOST_PATTERN = /hooks\.slack\.com|webhook\.site|discord(?:app)?\.com\/api\/webhooks|outlook\.office\.com\/webhook/i;
|
|
14
|
+
/** Scan backward from delimiters so long descriptions cannot cause regex backtracking. */
|
|
15
|
+
function containsUrlScheme(value) {
|
|
16
|
+
for (let delimiter = value.indexOf('://'); delimiter !== -1; delimiter = value.indexOf('://', delimiter + 3)) {
|
|
17
|
+
for (let cursor = delimiter - 1; cursor >= 0; cursor--) {
|
|
18
|
+
const character = value.charAt(cursor);
|
|
19
|
+
if (/[a-z]/i.test(character))
|
|
20
|
+
return true;
|
|
21
|
+
if (!/[0-9+.-]/.test(character))
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const CONNECTION_STRING_PATTERN = /^(?:jdbc|odbc):|(?:^|;)\s*(?:PWD|UID|DSN|DRIVER)\s*=/i;
|
|
28
|
+
const KEY_VALUE_SECRET_PATTERN = /(?:password|passwd|pwd|secret|api[_-]?key|access[_-]?key|private[_-]?key|client[_-]?secret|auth[_-]?token)\s*[=:]\s*\S+/i;
|
|
29
|
+
/** Well-known token prefixes (Slack, GitHub, OpenAI/Anthropic-style, AWS) and PEM blocks. */
|
|
30
|
+
const TOKEN_PREFIX_PATTERN = /\b(?:xox[a-z]-[A-Za-z0-9-]+|ghp_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|sk-[A-Za-z0-9_-]{16,}|AKIA[0-9A-Z]{16})\b|-----BEGIN [A-Z ]*PRIVATE KEY-----/;
|
|
31
|
+
/** Three dot-separated base64url segments of meaningful length — a JWT. */
|
|
32
|
+
const JWT_PATTERN = /\bey[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{5,}\b/;
|
|
33
|
+
/**
|
|
34
|
+
* Reason the string looks like a credential/webhook literal, or null when it
|
|
35
|
+
* is clean. `urlAllowed` permits a plain https URL (for `runbook`) while still
|
|
36
|
+
* rejecting webhook hosts and embedded credentials.
|
|
37
|
+
*/
|
|
38
|
+
export function credentialLikeReason(value, urlAllowed = false) {
|
|
39
|
+
if (WEBHOOK_HOST_PATTERN.test(value))
|
|
40
|
+
return 'looks like a webhook URL — use a symbolic alert channel reference';
|
|
41
|
+
if (CONNECTION_STRING_PATTERN.test(value))
|
|
42
|
+
return 'looks like a connection string — use a symbolic credential reference';
|
|
43
|
+
if (KEY_VALUE_SECRET_PATTERN.test(value))
|
|
44
|
+
return 'looks like an embedded credential — use a symbolic credential reference';
|
|
45
|
+
if (TOKEN_PREFIX_PATTERN.test(value))
|
|
46
|
+
return 'looks like a literal secret token — use a symbolic credential reference';
|
|
47
|
+
if (JWT_PATTERN.test(value))
|
|
48
|
+
return 'looks like a literal JWT — use a symbolic credential reference';
|
|
49
|
+
if (containsUrlScheme(value)) {
|
|
50
|
+
if (urlAllowed && value.startsWith('https://')) {
|
|
51
|
+
// An allowed URL must not smuggle credentials in its userinfo
|
|
52
|
+
// (https://user:password@host/…) — parse and reject, don't trust shape.
|
|
53
|
+
try {
|
|
54
|
+
const url = new URL(value);
|
|
55
|
+
if (url.username !== '' || url.password !== '') {
|
|
56
|
+
return 'URL carries userinfo credentials — use a symbolic credential reference';
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return 'must be a valid https:// URL';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return urlAllowed
|
|
65
|
+
? 'must be an https:// URL'
|
|
66
|
+
: 'URLs and connection strings are not allowed here — use a symbolic reference';
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Walk every string leaf of a parsed spec (post-YAML object tree) and collect
|
|
72
|
+
* credential-looking values. Object keys are grammar-constrained by the
|
|
73
|
+
* schemas, so only values are scanned.
|
|
74
|
+
*/
|
|
75
|
+
export function scanForCredentialLikeStrings(root) {
|
|
76
|
+
const findings = [];
|
|
77
|
+
walk(root, [], findings);
|
|
78
|
+
return findings;
|
|
79
|
+
}
|
|
80
|
+
function walk(value, path, findings) {
|
|
81
|
+
if (typeof value === 'string') {
|
|
82
|
+
const urlAllowed = URL_ALLOWED_PATHS.has(path.join('.'));
|
|
83
|
+
const reason = credentialLikeReason(value, urlAllowed);
|
|
84
|
+
if (reason)
|
|
85
|
+
findings.push({ path: path.join('.'), segments: path, reason });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (Array.isArray(value)) {
|
|
89
|
+
value.forEach((entry, index) => walk(entry, [...path, String(index)], findings));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (value !== null && typeof value === 'object') {
|
|
93
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
94
|
+
walk(entry, [...path, key], findings);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=secret-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-scan.js","sourceRoot":"","sources":["../src/secret-scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAcH,gGAAgG;AAChG,MAAM,iBAAiB,GAAwB,IAAI,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAA;AAEnE,MAAM,oBAAoB,GAAG,oGAAoG,CAAA;AAEjI,0FAA0F;AAC1F,SAAS,iBAAiB,CAAC,KAAa;IACtC,KAAK,IAAI,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,SAAS,KAAK,CAAC,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,CAAC,CAAC,EAAE,CAAC;QAC7G,KAAK,IAAI,MAAM,GAAG,SAAS,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;YACvD,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YACtC,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC;gBAAE,OAAO,IAAI,CAAA;YACzC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC;gBAAE,MAAK;QACxC,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,yBAAyB,GAAG,uDAAuD,CAAA;AAEzF,MAAM,wBAAwB,GAAG,0HAA0H,CAAA;AAE3J,6FAA6F;AAC7F,MAAM,oBAAoB,GAAG,4JAA4J,CAAA;AAEzL,2EAA2E;AAC3E,MAAM,WAAW,GAAG,iEAAiE,CAAA;AAErF;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAa,EAAE,UAAU,GAAG,KAAK;IACpE,IAAI,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,mEAAmE,CAAA;IAChH,IAAI,yBAAyB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,sEAAsE,CAAA;IACxH,IAAI,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,yEAAyE,CAAA;IAC1H,IAAI,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,yEAAyE,CAAA;IACtH,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,gEAAgE,CAAA;IACpG,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,IAAI,UAAU,IAAI,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/C,8DAA8D;YAC9D,wEAAwE;YACxE,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAA;gBAC1B,IAAI,GAAG,CAAC,QAAQ,KAAK,EAAE,IAAI,GAAG,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;oBAC/C,OAAO,wEAAwE,CAAA;gBACjF,CAAC;gBACD,OAAO,IAAI,CAAA;YACb,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,8BAA8B,CAAA;YACvC,CAAC;QACH,CAAC;QACD,OAAO,UAAU;YACf,CAAC,CAAC,yBAAyB;YAC3B,CAAC,CAAC,6EAA6E,CAAA;IACnF,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,4BAA4B,CAAC,IAAa;IACxD,MAAM,QAAQ,GAAwB,EAAE,CAAA;IACxC,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAA;IACxB,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,SAAS,IAAI,CAAC,KAAc,EAAE,IAAuB,EAAE,QAA6B;IAClF,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,UAAU,GAAG,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;QACxD,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,EAAE,UAAU,CAAC,CAAA;QACtD,IAAI,MAAM;YAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;QAC3E,OAAM;IACR,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAA;QAChF,OAAM;IACR,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAA;QACvC,CAAC;IACH,CAAC;AACH,CAAC"}
|