@voxgig/station 0.0.2 → 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.
- package/README.md +26 -32
- package/dist/src/Station.d.ts +1 -133
- package/dist/src/Station.js +46 -434
- package/dist/src/Station.js.map +1 -1
- package/dist/src/adapter.js +0 -54
- package/dist/src/adapter.js.map +1 -1
- package/dist/src/config-shape.js +0 -11
- package/dist/src/config-shape.js.map +1 -1
- package/dist/src/descriptor.js +0 -36
- package/dist/src/descriptor.js.map +1 -1
- package/dist/src/error.js +0 -5
- package/dist/src/error.js.map +1 -1
- package/dist/src/factory.d.ts +0 -9
- package/dist/src/factory.js +0 -37
- package/dist/src/factory.js.map +1 -1
- package/dist/src/feature.d.ts +0 -75
- package/dist/src/feature.js +0 -87
- package/dist/src/feature.js.map +1 -1
- package/dist/src/index.d.ts +1 -2
- package/dist/src/index.js +11 -22
- package/dist/src/index.js.map +1 -1
- package/dist/src/loader.d.ts +0 -29
- package/dist/src/loader.js +0 -66
- package/dist/src/loader.js.map +1 -1
- package/dist/src/profile.d.ts +10 -3
- package/dist/src/profile.js +97 -23
- package/dist/src/profile.js.map +1 -1
- package/dist/src/secrets.d.ts +5 -0
- package/dist/src/secrets.js +106 -32
- package/dist/src/secrets.js.map +1 -1
- package/dist/src/shape.d.ts +0 -22
- package/dist/src/shape.js +0 -113
- package/dist/src/shape.js.map +1 -1
- package/package.json +3 -4
- package/src/Station.ts +34 -441
- package/src/adapter.ts +0 -57
- package/src/config-shape.ts +0 -11
- package/src/descriptor.ts +0 -36
- package/src/error.ts +0 -5
- package/src/factory.ts +0 -37
- package/src/feature.ts +0 -87
- package/src/index.ts +1 -14
- package/src/loader.ts +0 -66
- package/src/profile.ts +115 -23
- package/src/secrets.ts +104 -32
- package/src/shape.ts +0 -113
- package/src/types.ts +0 -59
- package/dist/src/index.browser.d.ts +0 -14
- package/dist/src/index.browser.js +0 -64
- package/dist/src/index.browser.js.map +0 -1
- package/dist/src/profilecore.d.ts +0 -10
- package/dist/src/profilecore.js +0 -180
- package/dist/src/profilecore.js.map +0 -1
- package/src/index.browser.ts +0 -56
- package/src/profilecore.ts +0 -201
package/dist/src/profile.js
CHANGED
|
@@ -1,31 +1,112 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/* The FILE half of profile handling - finding and reading
|
|
3
|
-
* station.json - and the scope rule that hangs off where it was found.
|
|
4
|
-
* NODE-ONLY by construction (fs/os/path), which is the §2.2 / §17
|
|
5
|
-
* Phase 1 split: the browser entry never imports this module, and
|
|
6
|
-
* `Station` reaches it through the ConfigFileIO seam the node entry
|
|
7
|
-
* registers. The pure half (selectProfile, refapi, resolveProfile)
|
|
8
|
-
* lives in `profilecore.ts` and is re-exported here so every existing
|
|
9
|
-
* import of `./profile` keeps working. */
|
|
10
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
11
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
12
4
|
};
|
|
13
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.selectProfile =
|
|
6
|
+
exports.selectProfile = selectProfile;
|
|
7
|
+
exports.refapi = refapi;
|
|
8
|
+
exports.resolveProfile = resolveProfile;
|
|
15
9
|
exports.findConfigFile = findConfigFile;
|
|
16
10
|
exports.loadConfig = loadConfig;
|
|
17
11
|
exports.configScope = configScope;
|
|
18
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
19
13
|
const node_os_1 = __importDefault(require("node:os"));
|
|
20
14
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
+
const sekreto_1 = require("@voxgig/sekreto");
|
|
16
|
+
const descriptor_1 = require("./descriptor");
|
|
21
17
|
const error_1 = require("./error");
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
const shape_1 = require("./shape");
|
|
19
|
+
function selectProfile(optProfile) {
|
|
20
|
+
if (null != optProfile && '' !== optProfile) {
|
|
21
|
+
return optProfile;
|
|
22
|
+
}
|
|
23
|
+
const env = process.env.VOXGIG_STATION_PROFILE;
|
|
24
|
+
if (null != env && '' !== env) {
|
|
25
|
+
return env;
|
|
26
|
+
}
|
|
27
|
+
return 'default';
|
|
28
|
+
}
|
|
29
|
+
function refapi(ref) {
|
|
30
|
+
const at = String(ref).indexOf('$');
|
|
31
|
+
return -1 === at ? String(ref) : String(ref).slice(0, at);
|
|
32
|
+
}
|
|
33
|
+
function shallow(...sources) {
|
|
34
|
+
const out = {};
|
|
35
|
+
for (const src of sources) {
|
|
36
|
+
if (null == src || 'object' !== typeof src || Array.isArray(src)) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
for (const k of Object.keys(src)) {
|
|
40
|
+
out[k] = src[k];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
function resolveProfile(config, profileName) {
|
|
46
|
+
const profiles = config?.profiles || {};
|
|
47
|
+
const base = profiles['default'] || {};
|
|
48
|
+
const overlay = 'default' === profileName
|
|
49
|
+
? {} : (profiles[profileName] || {});
|
|
50
|
+
const providers = overlay.secrets?.providers ?? base.secrets?.providers ??
|
|
51
|
+
[{ kind: 'env' }];
|
|
52
|
+
const api = {};
|
|
53
|
+
for (const slug of sortedkeys(base.api, overlay.api)) {
|
|
54
|
+
api[slug] = shallow(base.api?.[slug], overlay.api?.[slug]);
|
|
55
|
+
}
|
|
56
|
+
// An api block declares no instance, so the ref set comes from the
|
|
57
|
+
// two `sdk` maps alone.
|
|
58
|
+
const sdk = {};
|
|
59
|
+
for (const ref of sortedkeys(base.sdk, overlay.sdk)) {
|
|
60
|
+
const a = refapi(ref);
|
|
61
|
+
const merged = shallow(base.api?.[a], base.sdk?.[ref], overlay.api?.[a], overlay.sdk?.[ref]);
|
|
62
|
+
for (const k of Object.keys(shape_1.BLOCK_DEFAULTS)) {
|
|
63
|
+
if (undefined === merged[k]) {
|
|
64
|
+
merged[k] = shape_1.BLOCK_DEFAULTS[k]();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
sdk[ref] = merged;
|
|
68
|
+
}
|
|
69
|
+
checksecrets(sdk, profileName);
|
|
70
|
+
return { name: profileName, providers, api, sdk };
|
|
71
|
+
}
|
|
72
|
+
function checksecrets(sdk, profileName) {
|
|
73
|
+
const refs = Object.keys(sdk).sort();
|
|
74
|
+
for (const ref of refs) {
|
|
75
|
+
const name = sdk[ref].secret;
|
|
76
|
+
if (null != name && !(0, sekreto_1.validname)(name)) {
|
|
77
|
+
throw new error_1.StationError('station_secret_name', 'profile "' + profileName + '" sdk "' + ref +
|
|
78
|
+
'": secret name rejected by sekreto: ' + JSON.stringify(name));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const seen = new Map();
|
|
82
|
+
for (const ref of refs) {
|
|
83
|
+
const written = sdk[ref].secret;
|
|
84
|
+
const derived = null == written || '' === written;
|
|
85
|
+
const name = derived ? (0, descriptor_1.secretnameDefault)(ref) : written;
|
|
86
|
+
const prior = seen.get(name);
|
|
87
|
+
if (undefined !== prior && (derived || prior.derived)) {
|
|
88
|
+
throw new error_1.StationError('station_secret_collision', 'profile "' + profileName + '": instances "' + prior.ref + '" and "' +
|
|
89
|
+
ref + '" both resolve to secret name "' + name +
|
|
90
|
+
'", so they would share one credential; name it explicitly on ' +
|
|
91
|
+
'each, or at the api level to share it deliberately (§5.1)');
|
|
92
|
+
}
|
|
93
|
+
if (undefined === prior) {
|
|
94
|
+
seen.set(name, { ref, derived });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function sortedkeys(...maps) {
|
|
99
|
+
const keys = new Set();
|
|
100
|
+
for (const m of maps) {
|
|
101
|
+
if (null == m || 'object' !== typeof m || Array.isArray(m)) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
for (const k of Object.keys(m)) {
|
|
105
|
+
keys.add(k);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return Array.from(keys).sort();
|
|
109
|
+
}
|
|
29
110
|
function findConfigFile(from) {
|
|
30
111
|
let dir = node_path_1.default.resolve(from || process.cwd());
|
|
31
112
|
for (;;) {
|
|
@@ -59,13 +140,6 @@ function loadConfig(from) {
|
|
|
59
140
|
throw new error_1.StationError('station_config_invalid', 'station.json at ' + file + ' is not valid JSON: ' + err.message);
|
|
60
141
|
}
|
|
61
142
|
}
|
|
62
|
-
// Which side of the review boundary the config came from (§6.3).
|
|
63
|
-
//
|
|
64
|
-
// `package` and `export` are honoured only from REPO-SCOPED config,
|
|
65
|
-
// because a user-level file is outside the repo's review boundary and a
|
|
66
|
-
// `package` key arriving from it names code to import. Everything else
|
|
67
|
-
// in a user-level config still applies - this narrows one key rather
|
|
68
|
-
// than distrusting the file.
|
|
69
143
|
function configScope(from) {
|
|
70
144
|
const file = findConfigFile(from);
|
|
71
145
|
if (null == file) {
|
package/dist/src/profile.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/profile.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/profile.ts"],"names":[],"mappings":"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"}
|
package/dist/src/secrets.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export declare function placeholderFor(slug: string): string;
|
|
2
|
+
export declare function encodeQueryValue(value: string): string;
|
|
3
|
+
export declare function substitute(url: string, headers: Record<string, any> | null | undefined, placeholder: string, value: string): {
|
|
4
|
+
url: string;
|
|
5
|
+
headers: Record<string, any>;
|
|
6
|
+
};
|
|
2
7
|
export declare class SecretBroker {
|
|
3
8
|
private sekreto;
|
|
4
9
|
private overrides;
|
package/dist/src/secrets.js
CHANGED
|
@@ -2,18 +2,116 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SecretBroker = void 0;
|
|
4
4
|
exports.placeholderFor = placeholderFor;
|
|
5
|
+
exports.encodeQueryValue = encodeQueryValue;
|
|
6
|
+
exports.substitute = substitute;
|
|
5
7
|
const sekreto_1 = require("@voxgig/sekreto");
|
|
8
|
+
const plugins_1 = require("@voxgig/sekreto/plugins");
|
|
6
9
|
const error_1 = require("./error");
|
|
7
10
|
// The secret broker (design §5): sekreto resolves, station places. The
|
|
8
11
|
// broker holds resolved values privately - they never enter options,
|
|
9
12
|
// events, or captures; the SDK sees only the placeholder.
|
|
10
|
-
// §7.2: keyed by INSTANCE. Two live instances of one api must have
|
|
11
|
-
// distinct placeholders or the injection seam cannot tell which
|
|
12
|
-
// credential a header wants. For an untagged instance this is the api
|
|
13
|
-
// slug, so the single-instance case is unchanged.
|
|
14
13
|
function placeholderFor(slug) {
|
|
15
14
|
return '[station:' + slug + ']';
|
|
16
15
|
}
|
|
16
|
+
const UNRESERVED = /^[A-Za-z0-9\-._~]$/;
|
|
17
|
+
const HEXPAIR = /^[0-9A-Fa-f]{2}$/;
|
|
18
|
+
// Every UTF-8 byte outside RFC 3986's unreserved set becomes upper-case %XX.
|
|
19
|
+
function encodeQueryValue(value) {
|
|
20
|
+
let out = '';
|
|
21
|
+
for (const byte of Buffer.from(value, 'utf8')) {
|
|
22
|
+
const ch = String.fromCharCode(byte);
|
|
23
|
+
out += byte < 0x80 && UNRESERVED.test(ch)
|
|
24
|
+
? ch
|
|
25
|
+
: '%' + byte.toString(16).toUpperCase().padStart(2, '0');
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
function matchAt(text, at, end, placeholder) {
|
|
30
|
+
let i = at;
|
|
31
|
+
for (const ch of placeholder) {
|
|
32
|
+
if (i < end && text[i] === ch) {
|
|
33
|
+
i++;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const hex = text.substring(i + 1, i + 3);
|
|
37
|
+
if (i + 3 <= end && '%' === text[i] && HEXPAIR.test(hex) &&
|
|
38
|
+
parseInt(hex, 16) === ch.charCodeAt(0)) {
|
|
39
|
+
i += 3;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
return -1;
|
|
43
|
+
}
|
|
44
|
+
return i - at;
|
|
45
|
+
}
|
|
46
|
+
// The placeholder becomes the value in header values and in the URL query,
|
|
47
|
+
// where an SDK percent-encodes it (any hex case) and the value is encoded in
|
|
48
|
+
// turn. The path and fragment are never touched; the headers are a copy.
|
|
49
|
+
function substitute(url, headers, placeholder, value) {
|
|
50
|
+
const out = { ...(headers || {}) };
|
|
51
|
+
if ('' === placeholder) {
|
|
52
|
+
return { url, headers: out };
|
|
53
|
+
}
|
|
54
|
+
for (const h of Object.keys(out)) {
|
|
55
|
+
const v = out[h];
|
|
56
|
+
if ('string' === typeof v && v.includes(placeholder)) {
|
|
57
|
+
out[h] = v.split(placeholder).join(value);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const q = url.indexOf('?');
|
|
61
|
+
const hash = url.indexOf('#');
|
|
62
|
+
if (-1 === q || (-1 !== hash && hash < q)) {
|
|
63
|
+
return { url, headers: out };
|
|
64
|
+
}
|
|
65
|
+
const end = -1 === hash ? url.length : hash;
|
|
66
|
+
const encoded = encodeQueryValue(value);
|
|
67
|
+
let query = '';
|
|
68
|
+
for (let i = q + 1; i < end;) {
|
|
69
|
+
const n = matchAt(url, i, end, placeholder);
|
|
70
|
+
if (0 < n) {
|
|
71
|
+
query += encoded;
|
|
72
|
+
i += n;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
query += url[i];
|
|
76
|
+
i++;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { url: url.substring(0, q + 1) + query + url.substring(end), headers: out };
|
|
80
|
+
}
|
|
81
|
+
// Every form of every value is matched against the original text and the
|
|
82
|
+
// union of the matches redacted, so no replacement can hide another form.
|
|
83
|
+
function redactValues(text, values) {
|
|
84
|
+
const forms = new Set();
|
|
85
|
+
for (const value of values) {
|
|
86
|
+
if ('' !== value) {
|
|
87
|
+
forms.add(value);
|
|
88
|
+
forms.add(encodeQueryValue(value));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const reach = (at) => {
|
|
92
|
+
let end = at;
|
|
93
|
+
for (const form of forms) {
|
|
94
|
+
if (at + form.length > end && text.startsWith(form, at)) {
|
|
95
|
+
end = at + form.length;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return end;
|
|
99
|
+
};
|
|
100
|
+
let out = '';
|
|
101
|
+
for (let i = 0; i < text.length;) {
|
|
102
|
+
let stop = reach(i);
|
|
103
|
+
if (stop === i) {
|
|
104
|
+
out += text[i++];
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
for (let j = i + 1; j < stop; j++) {
|
|
108
|
+
stop = Math.max(stop, reach(j));
|
|
109
|
+
}
|
|
110
|
+
out += '[redacted]';
|
|
111
|
+
i = stop;
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
17
115
|
class SecretBroker {
|
|
18
116
|
constructor(providers) {
|
|
19
117
|
// Values hoisted by adopt() from resident options.apikey (design §3.1).
|
|
@@ -21,27 +119,12 @@ class SecretBroker {
|
|
|
21
119
|
this.cache = new Map();
|
|
22
120
|
// Every value this broker ever held, for the exact-value scrub.
|
|
23
121
|
this.held = [];
|
|
24
|
-
this.sekreto = new sekreto_1.Sekreto({ providers });
|
|
122
|
+
this.sekreto = new sekreto_1.Sekreto({ plugins: plugins_1.allplugins, providers });
|
|
25
123
|
}
|
|
26
124
|
hoist(slug, value) {
|
|
27
125
|
this.overrides.set(slug, value);
|
|
28
126
|
this.held.push(value);
|
|
29
127
|
}
|
|
30
|
-
// Resolve the value for a plugin's secret name. Misses and store
|
|
31
|
-
// errors keep sekreto's distinction (design §5.2): a miss is
|
|
32
|
-
// station_secret_no_value, a store that could not answer is
|
|
33
|
-
// station_secret_error with sekreto's message intact - and never a
|
|
34
|
-
// retry against a weaker store (sekreto owns the chain).
|
|
35
|
-
// OVERRIDES ARE KEYED BY INSTANCE; THE RESOLUTION CACHE IS KEYED BY
|
|
36
|
-
// SECRET NAME (§5.3). A hoisted credential belongs to the one instance
|
|
37
|
-
// it was resident in, but a resolved VALUE belongs to the name it was
|
|
38
|
-
// resolved for - so several instances sharing one api-level `secret`
|
|
39
|
-
// cost one lookup rather than one each, and every client an
|
|
40
|
-
// auto-tagged `create()` produces shares the declared instance's entry
|
|
41
|
-
// instead of re-resolving per request.
|
|
42
|
-
//
|
|
43
|
-
// Keying the cache by instance instead is the defect this replaces: at
|
|
44
|
-
// 26 instances over 20 apis it turns one store round-trip into 26.
|
|
45
128
|
async value(slug, name) {
|
|
46
129
|
const override = this.overrides.get(slug);
|
|
47
130
|
if (null != override) {
|
|
@@ -65,19 +148,10 @@ class SecretBroker {
|
|
|
65
148
|
this.held.push(value);
|
|
66
149
|
return value;
|
|
67
150
|
}
|
|
68
|
-
//
|
|
69
|
-
// readability floor (design §7 as revised): on boundaries where the
|
|
70
|
-
// promise is absolute, every held value is scrubbed whatever its
|
|
71
|
-
// length. sekreto's own redact() runs too, covering values resolved
|
|
72
|
-
// by the underlying instance that station never held.
|
|
151
|
+
// sekreto's redact runs second; run first, it could split an encoded form.
|
|
73
152
|
scrub(text) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if ('' !== value) {
|
|
77
|
-
out = out.split(value).join('[redacted]');
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return out;
|
|
153
|
+
const raw = 'string' === typeof text ? text : '';
|
|
154
|
+
return this.sekreto.redact(redactValues(raw, this.held));
|
|
81
155
|
}
|
|
82
156
|
// Drop caches so the next resolve asks the stores again (rotation
|
|
83
157
|
// support rides on sekreto's refresh, design §5.3).
|
package/dist/src/secrets.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/secrets.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/secrets.ts"],"names":[],"mappings":"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"}
|
package/dist/src/shape.d.ts
CHANGED
|
@@ -3,15 +3,6 @@
|
|
|
3
3
|
export declare const PROFILE_DEFAULTS: {
|
|
4
4
|
[k: string]: () => any;
|
|
5
5
|
};
|
|
6
|
-
/** Block-level. `feature` is a container and safe early.
|
|
7
|
-
*
|
|
8
|
-
* `active` IS NOT, and that is the whole timing rule: a default
|
|
9
|
-
* synthesized into an OVERLAY block overwrites the base's real value
|
|
10
|
-
* and silently reactivates an integration the base deliberately barred
|
|
11
|
-
* (§3.3). So the two consumers read this same table at different
|
|
12
|
-
* moments - validateConfig before, to every block, because a block with
|
|
13
|
-
* no present keys is an open map; the resolver AFTER, to the merged
|
|
14
|
-
* instance, because an absent key must stay absent through the merge. */
|
|
15
6
|
export declare const BLOCK_DEFAULTS: {
|
|
16
7
|
[k: string]: () => any;
|
|
17
8
|
};
|
|
@@ -19,12 +10,6 @@ export declare const BLOCK_DEFAULTS: {
|
|
|
19
10
|
* inferred, so a reader does not have to work out which of the two it
|
|
20
11
|
* is, and so a port can assert it. */
|
|
21
12
|
export declare const MERGE_SENSITIVE: string[];
|
|
22
|
-
/** Materialize every documented default, DEFENSIVELY: a node that is
|
|
23
|
-
* not the kind it expects is left alone for validate to reject with a
|
|
24
|
-
* proper message. Pure data-in/data-out, which is what makes it
|
|
25
|
-
* portable to 22 languages and expressible in the corpus.
|
|
26
|
-
*
|
|
27
|
-
* THE NORMALIZED FORM IS AN INPUT TO VALIDATION AND TO NOTHING ELSE. */
|
|
28
13
|
export declare function normalizeConfig(raw: any): any;
|
|
29
14
|
/** `spec/config-shape.json`, §4.3 verbatim, through the shipped mirror.
|
|
30
15
|
* Every validate gets a CLONE: struct's validate consumes the spec it
|
|
@@ -32,11 +17,4 @@ export declare function normalizeConfig(raw: any): any;
|
|
|
32
17
|
* the module constant twice would validate the second config against a
|
|
33
18
|
* spec the first had already eaten. */
|
|
34
19
|
export declare function configShape(): any;
|
|
35
|
-
/** Normalize, then validate (§4.2). Raises `station_config_invalid`
|
|
36
|
-
* with EVERY struct error at once - an eighteen-instance config that
|
|
37
|
-
* touches three of them must not die because the eighteenth has a
|
|
38
|
-
* typo'd package name - then the §5.2 scans.
|
|
39
|
-
*
|
|
40
|
-
* Takes the NORMALIZED form. Handing it a raw config is the mistake
|
|
41
|
-
* §4.2 exists to prevent, so callers go through open()/loadConfig. */
|
|
42
20
|
export declare function validateConfig(normalized: any): any;
|
package/dist/src/shape.js
CHANGED
|
@@ -1,22 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/* The config grammar, as data (design §4).
|
|
3
|
-
*
|
|
4
|
-
* TWO STEPS, AND THE FIRST IS WHAT MAKES THE SECOND HONEST.
|
|
5
|
-
*
|
|
6
|
-
* struct drops the unexpected-key check for a map whose spec node ends
|
|
7
|
-
* up empty - "an empty spec object means the object can be open". An
|
|
8
|
-
* optional key is `['$ONE','$NIL', spec]`, and when the data does not
|
|
9
|
-
* carry that key the validator REMOVES it from the spec node. So a
|
|
10
|
-
* block whose keys are all optional degenerates into an open map
|
|
11
|
-
* exactly when the data has none of them, and `{"solar": {"bass": 1}}`
|
|
12
|
-
* validates clean - the one property the whole exercise is for,
|
|
13
|
-
* silently absent in the one case that matters.
|
|
14
|
-
*
|
|
15
|
-
* So: normalizeConfig materializes every documented default, and
|
|
16
|
-
* validateConfig then runs a shape WITH NO OPTIONAL CONTAINERS AT ALL.
|
|
17
|
-
* After normalization every container is present, so the shape can
|
|
18
|
-
* require them, so unexpected-key detection is live at every level and
|
|
19
|
-
* every error names its path. */
|
|
20
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
3
|
exports.MERGE_SENSITIVE = exports.BLOCK_DEFAULTS = exports.PROFILE_DEFAULTS = void 0;
|
|
22
4
|
exports.normalizeConfig = normalizeConfig;
|
|
@@ -27,9 +9,6 @@ const sekreto_1 = require("@voxgig/sekreto");
|
|
|
27
9
|
const config_shape_1 = require("./config-shape");
|
|
28
10
|
const error_1 = require("./error");
|
|
29
11
|
const descriptor_1 = require("./descriptor");
|
|
30
|
-
// ---------------------------------------------------------------------
|
|
31
|
-
// The defaults table - ONE table, two callers
|
|
32
|
-
// ---------------------------------------------------------------------
|
|
33
12
|
/** Profile-level containers. Safe to materialize early either way:
|
|
34
13
|
* they are containers, and a missing one merges as empty regardless. */
|
|
35
14
|
exports.PROFILE_DEFAULTS = {
|
|
@@ -38,15 +17,6 @@ exports.PROFILE_DEFAULTS = {
|
|
|
38
17
|
sdk: () => ({}),
|
|
39
18
|
feature: () => ({}),
|
|
40
19
|
};
|
|
41
|
-
/** Block-level. `feature` is a container and safe early.
|
|
42
|
-
*
|
|
43
|
-
* `active` IS NOT, and that is the whole timing rule: a default
|
|
44
|
-
* synthesized into an OVERLAY block overwrites the base's real value
|
|
45
|
-
* and silently reactivates an integration the base deliberately barred
|
|
46
|
-
* (§3.3). So the two consumers read this same table at different
|
|
47
|
-
* moments - validateConfig before, to every block, because a block with
|
|
48
|
-
* no present keys is an open map; the resolver AFTER, to the merged
|
|
49
|
-
* instance, because an absent key must stay absent through the merge. */
|
|
50
20
|
exports.BLOCK_DEFAULTS = {
|
|
51
21
|
active: () => true,
|
|
52
22
|
feature: () => ({}),
|
|
@@ -58,12 +28,6 @@ exports.MERGE_SENSITIVE = ['active'];
|
|
|
58
28
|
// ---------------------------------------------------------------------
|
|
59
29
|
// normalizeConfig
|
|
60
30
|
// ---------------------------------------------------------------------
|
|
61
|
-
/** Materialize every documented default, DEFENSIVELY: a node that is
|
|
62
|
-
* not the kind it expects is left alone for validate to reject with a
|
|
63
|
-
* proper message. Pure data-in/data-out, which is what makes it
|
|
64
|
-
* portable to 22 languages and expressible in the corpus.
|
|
65
|
-
*
|
|
66
|
-
* THE NORMALIZED FORM IS AN INPUT TO VALIDATION AND TO NOTHING ELSE. */
|
|
67
31
|
function normalizeConfig(raw) {
|
|
68
32
|
if (!ismap(raw)) {
|
|
69
33
|
return raw;
|
|
@@ -91,7 +55,6 @@ function normalizeConfig(raw) {
|
|
|
91
55
|
prof[k] = exports.PROFILE_DEFAULTS[k]();
|
|
92
56
|
}
|
|
93
57
|
}
|
|
94
|
-
// A `secrets` written without `providers` still gets the chain.
|
|
95
58
|
if (ismap(prof.secrets) && undefined === prof.secrets.providers) {
|
|
96
59
|
prof.secrets = { ...prof.secrets, providers: [{ kind: 'env' }] };
|
|
97
60
|
}
|
|
@@ -123,16 +86,6 @@ function normalizeConfig(raw) {
|
|
|
123
86
|
out.profiles = profiles;
|
|
124
87
|
return out;
|
|
125
88
|
}
|
|
126
|
-
/** Per feature entry, at every level: `active` -> true.
|
|
127
|
-
*
|
|
128
|
-
* A FEATURE NAMED IN THE CONFIG IS ONE YOU ARE ASKING FOR. The SDK's
|
|
129
|
-
* own default is `active: false` for all but `log`, and
|
|
130
|
-
* `{"retry": {"retries": 3}}` plainly means "retry, with three
|
|
131
|
-
* attempts". It also keeps the feature map closed, for the same reason
|
|
132
|
-
* every other block needs one present key.
|
|
133
|
-
*
|
|
134
|
-
* Defensive like the rest: a non-map is returned untouched for validate
|
|
135
|
-
* to reject by path. */
|
|
136
89
|
function normfeatures(f) {
|
|
137
90
|
if (!ismap(f)) {
|
|
138
91
|
return f;
|
|
@@ -162,30 +115,9 @@ const CREDENTIAL_KEYS = [
|
|
|
162
115
|
'apikey', 'auth', 'authorization', 'token',
|
|
163
116
|
'secret', 'password', 'credential', 'bearer',
|
|
164
117
|
];
|
|
165
|
-
/** The suffix rule catches `access_key`, `X-Api-Token` and friends in
|
|
166
|
-
* one rule rather than a growing list of spellings. */
|
|
167
118
|
const CREDENTIAL_SUFFIX = ['_KEY', '_TOKEN', '_SECRET', '_PASSWORD'];
|
|
168
|
-
/** §5.2's backstop, and it is stated as one rather than as a grammar.
|
|
169
|
-
* `validname()` is a NAME grammar, not a credential filter: it rejects
|
|
170
|
-
* uppercase, hyphens, `+`, `/` and `=`, so it excludes most real
|
|
171
|
-
* credential formats - but a lowercase hex token passes it cleanly. A
|
|
172
|
-
* character class cannot tell a name from a secret.
|
|
173
|
-
*
|
|
174
|
-
* Derived names break on every separator (`voxgig_solardemo.apikey`
|
|
175
|
-
* runs 6/9/6) and a hand-written name for a human to read does too; a
|
|
176
|
-
* 24-character unbroken run is not a name anybody writes. Note this is
|
|
177
|
-
* a RUN bound, not a length bound: `acme_internal_billing_service.apikey`
|
|
178
|
-
* is 36 characters and passes, which is the false positive a naive
|
|
179
|
-
* length bound would produce. */
|
|
180
119
|
const RUN_BOUND = 24;
|
|
181
120
|
const UNBROKEN_RUN = new RegExp('[A-Za-z0-9]{' + RUN_BOUND + ',}');
|
|
182
|
-
/** Normalize, then validate (§4.2). Raises `station_config_invalid`
|
|
183
|
-
* with EVERY struct error at once - an eighteen-instance config that
|
|
184
|
-
* touches three of them must not die because the eighteenth has a
|
|
185
|
-
* typo'd package name - then the §5.2 scans.
|
|
186
|
-
*
|
|
187
|
-
* Takes the NORMALIZED form. Handing it a raw config is the mistake
|
|
188
|
-
* §4.2 exists to prevent, so callers go through open()/loadConfig. */
|
|
189
121
|
function validateConfig(normalized) {
|
|
190
122
|
const errs = [];
|
|
191
123
|
(0, struct_1.validate)(normalized, configShape(), { errs });
|
|
@@ -195,11 +127,6 @@ function validateConfig(normalized) {
|
|
|
195
127
|
scanConfig(normalized);
|
|
196
128
|
return normalized;
|
|
197
129
|
}
|
|
198
|
-
/** `plugin` is REMOVED, not aliased (§3.4) - a deprecated alias would
|
|
199
|
-
* be a second grammar for one concept in 17 ports. The shape already
|
|
200
|
-
* rejects it as an unexpected key; this says what to rename, because
|
|
201
|
-
* "unexpected key: plugin" alone does not, and the migration for a
|
|
202
|
-
* single-instance project is exactly this one rename. */
|
|
203
130
|
function renamehint(cfg) {
|
|
204
131
|
const profiles = ismap(cfg) && ismap(cfg.profiles) ? cfg.profiles : {};
|
|
205
132
|
const hit = Object.keys(profiles)
|
|
@@ -211,8 +138,6 @@ function renamehint(cfg) {
|
|
|
211
138
|
hit.map((p) => 'profiles.' + p).join(', ') +
|
|
212
139
|
' - the keys are unchanged, an untagged ref IS an api slug (§3.4)';
|
|
213
140
|
}
|
|
214
|
-
/** The §5.2 scans, over the parts of the grammar that hold arbitrary
|
|
215
|
-
* data. Everything else is closed by construction and needs no scan. */
|
|
216
141
|
function scanConfig(cfg) {
|
|
217
142
|
const secrets = [];
|
|
218
143
|
const reserved = [];
|
|
@@ -241,14 +166,8 @@ function scanConfig(cfg) {
|
|
|
241
166
|
if (undefined !== block.secret) {
|
|
242
167
|
secretvalue(block.secret, bpath + '.secret', secrets);
|
|
243
168
|
}
|
|
244
|
-
// `options` is passthrough to a generated constructor, so it is
|
|
245
|
-
// the one place a value can hide.
|
|
246
169
|
scan(block.options, bpath + '.options', secrets, reserved);
|
|
247
170
|
checkfeatures(block.feature, bpath + '.feature', secrets, reserved, invalid);
|
|
248
|
-
// §4.4's explicit checks, applied where the shape cannot reach,
|
|
249
|
-
// raising the same code the shape would - and pinned in the
|
|
250
|
-
// corpus so each workaround is removed deliberately when struct
|
|
251
|
-
// is fixed rather than forgotten.
|
|
252
171
|
checkpolicy(block.policy, bpath + '.policy', invalid);
|
|
253
172
|
}
|
|
254
173
|
}
|
|
@@ -284,19 +203,6 @@ function checkfeatures(f, path, secrets, reserved, invalid) {
|
|
|
284
203
|
scan(f[name], fpath, secrets, reserved);
|
|
285
204
|
}
|
|
286
205
|
}
|
|
287
|
-
/** The policy block's §4.4 workarounds, in one place because they are
|
|
288
|
-
* one class of gap: struct cannot check what its own defects hide.
|
|
289
|
-
*
|
|
290
|
-
* - `hosts`, `allow.op` and `allow.method` are `$CHILD` string lists,
|
|
291
|
-
* so element 0 escapes the shape (see firstelement below).
|
|
292
|
-
* - `budget` is a map whose keys are ALL optional scalars, and struct
|
|
293
|
-
* removes an unsatisfied optional key from the spec node - so
|
|
294
|
-
* `budget: {rp: 1}` degenerates the spec into an open map and the
|
|
295
|
-
* typo passes. `allow` does not have this problem (its `$CHILD` keys
|
|
296
|
-
* stay in the spec whether or not the data carries them, keeping the
|
|
297
|
-
* map closed), and neither does `policy` itself (`hosts` anchors it);
|
|
298
|
-
* `budget` alone needs the explicit unexpected-key check, phrased as
|
|
299
|
-
* struct would phrase it. */
|
|
300
206
|
const BUDGET_KEYS = ['concurrency', 'rps'];
|
|
301
207
|
function checkpolicy(policy, path, invalid) {
|
|
302
208
|
if (!ismap(policy)) {
|
|
@@ -318,19 +224,6 @@ function checkpolicy(policy, path, invalid) {
|
|
|
318
224
|
}
|
|
319
225
|
}
|
|
320
226
|
}
|
|
321
|
-
/** §4.4: `$CHILD` in list mode DOES NOT VALIDATE ELEMENT 0. Verified:
|
|
322
|
-
* `["a", 1]` fails at index 1, `[1]` passes. validate_CHILD installs the
|
|
323
|
-
* template across the data's indices, sets keyI = 0 and returns element
|
|
324
|
-
* 0 as the slot value, and the injection loop resumes past it. An
|
|
325
|
-
* upstream struct defect; filing it is a deliverable of this plan.
|
|
326
|
-
*
|
|
327
|
-
* It reaches EVERY string list in the shape. §4.4 says the only one left
|
|
328
|
-
* is `policy.hosts` because the profile-level `order` list is gone - but
|
|
329
|
-
* §8.4's per-feature `order` carries `before`/`after` string lists, so
|
|
330
|
-
* the reach is three lists, not one, and §4.5 pins `order: [7]`
|
|
331
|
-
* accordingly. Applied where the shape cannot reach, raising the same
|
|
332
|
-
* code the shape would, and pinned in the corpus so the workaround is
|
|
333
|
-
* removed deliberately when struct is fixed rather than forgotten. */
|
|
334
227
|
function firstelement(list, path, invalid) {
|
|
335
228
|
if (!Array.isArray(list) || 0 === list.length) {
|
|
336
229
|
return;
|
|
@@ -360,8 +253,6 @@ function scan(node, path, secrets, reserved) {
|
|
|
360
253
|
for (const key of Object.keys(node)) {
|
|
361
254
|
const kpath = path + '.' + key;
|
|
362
255
|
const val = node[key];
|
|
363
|
-
// §8.6: station owns feature composition, so an `options.feature`
|
|
364
|
-
// in a declarative config is a second, unreconciled ordering input.
|
|
365
256
|
if ('feature' === key) {
|
|
366
257
|
reserved.push(kpath + ' is reserved: configure features under the ' +
|
|
367
258
|
'block\'s own `feature` key, not through `options`');
|
|
@@ -387,10 +278,6 @@ function credentialkey(key) {
|
|
|
387
278
|
const tok = (0, descriptor_1.envtoken)(key);
|
|
388
279
|
return CREDENTIAL_SUFFIX.some((s) => tok.endsWith(s));
|
|
389
280
|
}
|
|
390
|
-
/** A `secret`-named key holds a NAME, and that exemption is not a
|
|
391
|
-
* loophole - it is the whole design. Two checks, not one, and they live
|
|
392
|
-
* in the same three lines precisely so a port cannot implement only the
|
|
393
|
-
* first and inherit the gap the second exists to close. */
|
|
394
281
|
function secretvalue(val, path, secrets) {
|
|
395
282
|
if ('string' !== typeof val) {
|
|
396
283
|
secrets.push(path + ' must be a secret name (a string), but found ' +
|