@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.
Files changed (55) hide show
  1. package/README.md +26 -32
  2. package/dist/src/Station.d.ts +1 -133
  3. package/dist/src/Station.js +46 -434
  4. package/dist/src/Station.js.map +1 -1
  5. package/dist/src/adapter.js +0 -54
  6. package/dist/src/adapter.js.map +1 -1
  7. package/dist/src/config-shape.js +0 -11
  8. package/dist/src/config-shape.js.map +1 -1
  9. package/dist/src/descriptor.js +0 -36
  10. package/dist/src/descriptor.js.map +1 -1
  11. package/dist/src/error.js +0 -5
  12. package/dist/src/error.js.map +1 -1
  13. package/dist/src/factory.d.ts +0 -9
  14. package/dist/src/factory.js +0 -37
  15. package/dist/src/factory.js.map +1 -1
  16. package/dist/src/feature.d.ts +0 -75
  17. package/dist/src/feature.js +0 -87
  18. package/dist/src/feature.js.map +1 -1
  19. package/dist/src/index.d.ts +1 -2
  20. package/dist/src/index.js +11 -22
  21. package/dist/src/index.js.map +1 -1
  22. package/dist/src/loader.d.ts +0 -29
  23. package/dist/src/loader.js +0 -66
  24. package/dist/src/loader.js.map +1 -1
  25. package/dist/src/profile.d.ts +10 -3
  26. package/dist/src/profile.js +97 -23
  27. package/dist/src/profile.js.map +1 -1
  28. package/dist/src/secrets.d.ts +5 -0
  29. package/dist/src/secrets.js +106 -32
  30. package/dist/src/secrets.js.map +1 -1
  31. package/dist/src/shape.d.ts +0 -22
  32. package/dist/src/shape.js +0 -113
  33. package/dist/src/shape.js.map +1 -1
  34. package/package.json +3 -4
  35. package/src/Station.ts +34 -441
  36. package/src/adapter.ts +0 -57
  37. package/src/config-shape.ts +0 -11
  38. package/src/descriptor.ts +0 -36
  39. package/src/error.ts +0 -5
  40. package/src/factory.ts +0 -37
  41. package/src/feature.ts +0 -87
  42. package/src/index.ts +1 -14
  43. package/src/loader.ts +0 -66
  44. package/src/profile.ts +115 -23
  45. package/src/secrets.ts +104 -32
  46. package/src/shape.ts +0 -113
  47. package/src/types.ts +0 -59
  48. package/dist/src/index.browser.d.ts +0 -14
  49. package/dist/src/index.browser.js +0 -64
  50. package/dist/src/index.browser.js.map +0 -1
  51. package/dist/src/profilecore.d.ts +0 -10
  52. package/dist/src/profilecore.js +0 -180
  53. package/dist/src/profilecore.js.map +0 -1
  54. package/src/index.browser.ts +0 -56
  55. package/src/profilecore.ts +0 -201
@@ -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 = exports.resolveProfile = exports.refapi = void 0;
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
- var profilecore_1 = require("./profilecore");
23
- Object.defineProperty(exports, "refapi", { enumerable: true, get: function () { return profilecore_1.refapi; } });
24
- Object.defineProperty(exports, "resolveProfile", { enumerable: true, get: function () { return profilecore_1.resolveProfile; } });
25
- Object.defineProperty(exports, "selectProfile", { enumerable: true, get: function () { return profilecore_1.selectProfile; } });
26
- // station.json lookup: cwd upward to the repo root, then
27
- // ~/.voxgig/station.json (design §3.5). A repo root is where .git lives;
28
- // with no repo the walk stops at the filesystem root.
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) {
@@ -1 +1 @@
1
- {"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/profile.ts"],"names":[],"mappings":";AAAA;;;;;;;0CAO0C;;;;;;AAiB1C,wCAYC;AAED,gCAcC;AASD,kCAKC;AAzDD,sDAAwB;AACxB,sDAAwB;AACxB,0DAA4B;AAE5B,mCAAsC;AAGtC,6CAEsB;AADpB,qGAAA,MAAM,OAAA;AAAE,6GAAA,cAAc,OAAA;AAAE,4GAAA,aAAa,OAAA;AAIvC,yDAAyD;AACzD,yEAAyE;AACzE,sDAAsD;AACtD,SAAgB,cAAc,CAAC,IAAa;IAC1C,IAAI,GAAG,GAAG,mBAAI,CAAC,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IAC7C,SAAU,CAAC;QACT,MAAM,SAAS,GAAG,mBAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAA;QAChD,IAAI,iBAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAAC,OAAO,SAAS,CAAA;QAAC,CAAC;QAClD,MAAM,UAAU,GAAG,iBAAE,CAAC,UAAU,CAAC,mBAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAA;QACxD,MAAM,MAAM,GAAG,mBAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,UAAU,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YAAC,MAAK;QAAC,CAAC;QAC3C,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;IACD,MAAM,IAAI,GAAG,mBAAI,CAAC,IAAI,CAAC,iBAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,cAAc,CAAC,CAAA;IAC/D,OAAO,iBAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;AAC1C,CAAC;AAED,SAAgB,UAAU,CAAC,IAAa;IACtC,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QAAC,OAAO,IAAI,CAAA;IAAC,CAAC;IACjC,MAAM,IAAI,GAAG,iBAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAC1C,mEAAmE;IACnE,mEAAmE;IACnE,kEAAkE;IAClE,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IACD,OAAO,GAAQ,EAAE,CAAC;QAChB,MAAM,IAAI,oBAAY,CAAC,wBAAwB,EAC7C,kBAAkB,GAAG,IAAI,GAAG,sBAAsB,GAAG,GAAG,CAAC,OAAO,CAAC,CAAA;IACrE,CAAC;AACH,CAAC;AAED,iEAAiE;AACjE,EAAE;AACF,oEAAoE;AACpE,wEAAwE;AACxE,uEAAuE;AACvE,qEAAqE;AACrE,6BAA6B;AAC7B,SAAgB,WAAW,CAAC,IAAa;IACvC,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QAAC,OAAO,MAAM,CAAA;IAAC,CAAC;IACnC,MAAM,IAAI,GAAG,mBAAI,CAAC,IAAI,CAAC,iBAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,cAAc,CAAC,CAAA;IAC/D,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;AACxC,CAAC"}
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/profile.ts"],"names":[],"mappings":";;;;;AAYA,sCAKC;AAED,wBAGC;AAoBD,wCAsCC;AA2CD,wCAYC;AAED,gCAcC;AAED,kCAKC;AA7JD,sDAAwB;AACxB,sDAAwB;AACxB,0DAA4B;AAE5B,6CAA2C;AAE3C,6CAAgD;AAChD,mCAAsC;AACtC,mCAAwC;AAGxC,SAAgB,aAAa,CAAC,UAAmB;IAC/C,IAAI,IAAI,IAAI,UAAU,IAAI,EAAE,KAAK,UAAU,EAAE,CAAC;QAAC,OAAO,UAAU,CAAA;IAAC,CAAC;IAClE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAA;IAC9C,IAAI,IAAI,IAAI,GAAG,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;QAAC,OAAO,GAAG,CAAA;IAAC,CAAC;IAC7C,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,SAAgB,MAAM,CAAC,GAAW;IAChC,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACnC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AAC3D,CAAC;AAWD,SAAS,OAAO,CAAC,GAAG,OAAc;IAChC,MAAM,GAAG,GAAQ,EAAE,CAAA;IACnB,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK,OAAO,GAAG,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAAC,SAAQ;QAAC,CAAC;QAC9E,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;QAAC,CAAC;IACvD,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAgB,cAAc,CAC5B,MAA4B,EAAE,WAAmB;IAEjD,MAAM,QAAQ,GAAG,MAAM,EAAE,QAAQ,IAAI,EAAE,CAAA;IACvC,MAAM,IAAI,GAAQ,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,CAAA;IAC3C,MAAM,OAAO,GAAQ,SAAS,KAAK,WAAW;QAC5C,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAA;IAEtC,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,EAAE,SAAS,IAAI,IAAI,CAAC,OAAO,EAAE,SAAS;QACrE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAEnB,MAAM,GAAG,GAA6B,EAAE,CAAA;IACxC,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACrD,GAAG,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;IAC5D,CAAC;IAED,mEAAmE;IACnE,wBAAwB;IACxB,MAAM,GAAG,GAA6B,EAAE,CAAA;IACxC,KAAK,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;QACrB,MAAM,MAAM,GAAG,OAAO,CACpB,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EACb,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,EACf,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAChB,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CACnB,CAAA;QAED,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,sBAAc,CAAC,EAAE,CAAC;YAC5C,IAAI,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;gBAAC,MAAM,CAAC,CAAC,CAAC,GAAG,sBAAc,CAAC,CAAC,CAAC,EAAE,CAAA;YAAC,CAAC;QAClE,CAAC;QAED,GAAG,CAAC,GAAG,CAAC,GAAG,MAAM,CAAA;IACnB,CAAC;IAED,YAAY,CAAC,GAAG,EAAE,WAAW,CAAC,CAAA;IAE9B,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,CAAA;AACnD,CAAC;AAED,SAAS,YAAY,CACnB,GAA6B,EAAE,WAAmB;IAElD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;IAEpC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,CAAA;QAC5B,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,oBAAY,CAAC,qBAAqB,EAC1C,WAAW,GAAG,WAAW,GAAG,SAAS,GAAG,GAAG;gBAC3C,sCAAsC,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;QAClE,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,GAAG,EAA6C,CAAA;IACjE,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,CAAA;QAC/B,MAAM,OAAO,GAAG,IAAI,IAAI,OAAO,IAAI,EAAE,KAAK,OAAO,CAAA;QACjD,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,IAAA,8BAAiB,EAAC,GAAG,CAAC,CAAC,CAAC,CAAE,OAAkB,CAAA;QAEnE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAC5B,IAAI,SAAS,KAAK,KAAK,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YACtD,MAAM,IAAI,oBAAY,CAAC,0BAA0B,EAC/C,WAAW,GAAG,WAAW,GAAG,gBAAgB,GAAG,KAAK,CAAC,GAAG,GAAG,SAAS;gBACpE,GAAG,GAAG,iCAAiC,GAAG,IAAI;gBAC9C,+DAA+D;gBAC/D,2DAA2D,CAAC,CAAA;QAChE,CAAC;QACD,IAAI,SAAS,KAAK,KAAK,EAAE,CAAC;YAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAA;QAAC,CAAC;IAC/D,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,GAAG,IAAW;IAChC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,IAAI,IAAI,CAAC,IAAI,QAAQ,KAAK,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;YAAC,SAAQ;QAAC,CAAC;QACxE,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;QAAC,CAAC;IACjD,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;AAChC,CAAC;AAED,SAAgB,cAAc,CAAC,IAAa;IAC1C,IAAI,GAAG,GAAG,mBAAI,CAAC,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IAC7C,SAAU,CAAC;QACT,MAAM,SAAS,GAAG,mBAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAA;QAChD,IAAI,iBAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAAC,OAAO,SAAS,CAAA;QAAC,CAAC;QAClD,MAAM,UAAU,GAAG,iBAAE,CAAC,UAAU,CAAC,mBAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAA;QACxD,MAAM,MAAM,GAAG,mBAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,UAAU,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YAAC,MAAK;QAAC,CAAC;QAC3C,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;IACD,MAAM,IAAI,GAAG,mBAAI,CAAC,IAAI,CAAC,iBAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,cAAc,CAAC,CAAA;IAC/D,OAAO,iBAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;AAC1C,CAAC;AAED,SAAgB,UAAU,CAAC,IAAa;IACtC,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QAAC,OAAO,IAAI,CAAA;IAAC,CAAC;IACjC,MAAM,IAAI,GAAG,iBAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAC1C,mEAAmE;IACnE,mEAAmE;IACnE,kEAAkE;IAClE,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IACD,OAAO,GAAQ,EAAE,CAAC;QAChB,MAAM,IAAI,oBAAY,CAAC,wBAAwB,EAC7C,kBAAkB,GAAG,IAAI,GAAG,sBAAsB,GAAG,GAAG,CAAC,OAAO,CAAC,CAAA;IACrE,CAAC;AACH,CAAC;AAED,SAAgB,WAAW,CAAC,IAAa;IACvC,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QAAC,OAAO,MAAM,CAAA;IAAC,CAAC;IACnC,MAAM,IAAI,GAAG,mBAAI,CAAC,IAAI,CAAC,iBAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,cAAc,CAAC,CAAA;IAC/D,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;AACxC,CAAC"}
@@ -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;
@@ -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
- // Exact-value scrub, deliberately WITHOUT sekreto's four-character
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
- let out = this.sekreto.redact(text);
75
- for (const value of this.held) {
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).
@@ -1 +1 @@
1
- {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/secrets.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/secrets.ts"],"names":[],"mappings":"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"}
@@ -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 ' +