@finsys/core 4.7.0 → 4.8.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/dist/index.d.cts CHANGED
@@ -855,6 +855,22 @@ interface CanonicalFieldSpec {
855
855
  * id. Uniquely-declared fields need no `fact` (but may carry one).
856
856
  */
857
857
  readonly fact?: string;
858
+ /**
859
+ * Field kind — a semantic refinement of `type`. Currently the only
860
+ * kind is `"enum"`: the field's value is one label out of a closed
861
+ * set. The category declares ONLY that the field is enumerated —
862
+ * never the values, never an ordering. Value sets are vendor
863
+ * territory: each adapter declares the exact labels it emits in its
864
+ * manifest's `enumValues` (host-validated), because two vendors
865
+ * implementing the same category may bucket differently. Ordering
866
+ * and scoring interpretation live even further out, in the consumer
867
+ * (an eval model's per-value mapping) — an enum label is data, what
868
+ * it is worth is opinion, and opinions don't belong in the data
869
+ * contract. An enum field MUST be `type: "string"` (labels are
870
+ * string-normalized) and MUST NOT declare a `range` (labels are
871
+ * unordered).
872
+ */
873
+ readonly kind?: "enum";
858
874
  }
859
875
  /**
860
876
  * Per-category schema bundle. Used by:
@@ -1629,6 +1645,34 @@ interface AdapterManifest {
1629
1645
  * adapter is implemented.
1630
1646
  */
1631
1647
  readonly periods?: ReadonlyArray<PeriodDeclaration>;
1648
+ /**
1649
+ * Vendor-specific value sets for the enum-kind fields this adapter
1650
+ * produces. The category declares only THAT a field is enumerated
1651
+ * (`kind: "enum"` — no values, no ordering); the manifest declares
1652
+ * exactly WHICH labels this vendor emits, because two vendors
1653
+ * implementing the same category may bucket differently. Scoring
1654
+ * interpretation lives further out still, in the consumer (an eval
1655
+ * model's per-value mapping with a mandatory fallback) — never here.
1656
+ *
1657
+ * Host-validated at registration (same posture as `produces` ⊆
1658
+ * category fields):
1659
+ * - every key MUST appear in `produces`;
1660
+ * - every key MUST be declared `kind: "enum"` by the adapter's
1661
+ * category — a non-enum field with declared values is refused;
1662
+ * - every enum-kind field in `produces` MUST have an entry here —
1663
+ * an adapter that promises an enum field but not its labels
1664
+ * gives clients nothing to render or map against;
1665
+ * - each value set is a non-empty array of unique, string-
1666
+ * normalized labels (non-empty, no leading/trailing whitespace).
1667
+ *
1668
+ * At runtime the adapter emits labels from its declared set
1669
+ * VERBATIM; the host refuses out-of-set values at ingest, so a
1670
+ * vendor-side vocabulary change is a loud manifest-version bump,
1671
+ * never silent data drift. Clients (form-spec and eval-model
1672
+ * editors) read these sets through the registry-metadata surface to
1673
+ * offer the labels without hardcoding any vendor's vocabulary.
1674
+ */
1675
+ readonly enumValues?: Readonly<Record<CanonicalFieldName, ReadonlyArray<string>>>;
1632
1676
  /**
1633
1677
  * Optional free-form notes — useful for partner-side documentation
1634
1678
  * (where to find the adapter's source, who owns it, what version of
package/dist/index.d.ts CHANGED
@@ -855,6 +855,22 @@ interface CanonicalFieldSpec {
855
855
  * id. Uniquely-declared fields need no `fact` (but may carry one).
856
856
  */
857
857
  readonly fact?: string;
858
+ /**
859
+ * Field kind — a semantic refinement of `type`. Currently the only
860
+ * kind is `"enum"`: the field's value is one label out of a closed
861
+ * set. The category declares ONLY that the field is enumerated —
862
+ * never the values, never an ordering. Value sets are vendor
863
+ * territory: each adapter declares the exact labels it emits in its
864
+ * manifest's `enumValues` (host-validated), because two vendors
865
+ * implementing the same category may bucket differently. Ordering
866
+ * and scoring interpretation live even further out, in the consumer
867
+ * (an eval model's per-value mapping) — an enum label is data, what
868
+ * it is worth is opinion, and opinions don't belong in the data
869
+ * contract. An enum field MUST be `type: "string"` (labels are
870
+ * string-normalized) and MUST NOT declare a `range` (labels are
871
+ * unordered).
872
+ */
873
+ readonly kind?: "enum";
858
874
  }
859
875
  /**
860
876
  * Per-category schema bundle. Used by:
@@ -1629,6 +1645,34 @@ interface AdapterManifest {
1629
1645
  * adapter is implemented.
1630
1646
  */
1631
1647
  readonly periods?: ReadonlyArray<PeriodDeclaration>;
1648
+ /**
1649
+ * Vendor-specific value sets for the enum-kind fields this adapter
1650
+ * produces. The category declares only THAT a field is enumerated
1651
+ * (`kind: "enum"` — no values, no ordering); the manifest declares
1652
+ * exactly WHICH labels this vendor emits, because two vendors
1653
+ * implementing the same category may bucket differently. Scoring
1654
+ * interpretation lives further out still, in the consumer (an eval
1655
+ * model's per-value mapping with a mandatory fallback) — never here.
1656
+ *
1657
+ * Host-validated at registration (same posture as `produces` ⊆
1658
+ * category fields):
1659
+ * - every key MUST appear in `produces`;
1660
+ * - every key MUST be declared `kind: "enum"` by the adapter's
1661
+ * category — a non-enum field with declared values is refused;
1662
+ * - every enum-kind field in `produces` MUST have an entry here —
1663
+ * an adapter that promises an enum field but not its labels
1664
+ * gives clients nothing to render or map against;
1665
+ * - each value set is a non-empty array of unique, string-
1666
+ * normalized labels (non-empty, no leading/trailing whitespace).
1667
+ *
1668
+ * At runtime the adapter emits labels from its declared set
1669
+ * VERBATIM; the host refuses out-of-set values at ingest, so a
1670
+ * vendor-side vocabulary change is a loud manifest-version bump,
1671
+ * never silent data drift. Clients (form-spec and eval-model
1672
+ * editors) read these sets through the registry-metadata surface to
1673
+ * offer the labels without hardcoding any vendor's vocabulary.
1674
+ */
1675
+ readonly enumValues?: Readonly<Record<CanonicalFieldName, ReadonlyArray<string>>>;
1632
1676
  /**
1633
1677
  * Optional free-form notes — useful for partner-side documentation
1634
1678
  * (where to find the adapter's source, who owns it, what version of
package/dist/index.js CHANGED
@@ -10211,7 +10211,7 @@ function applyAggregation(op, instances) {
10211
10211
 
10212
10212
  // src/data/adapter-categories.json
10213
10213
  var adapter_categories_default = {
10214
- schemaVersion: "1.1.0",
10214
+ schemaVersion: "1.2.0",
10215
10215
  categories: [
10216
10216
  {
10217
10217
  id: "telco-carrier",
@@ -10263,6 +10263,30 @@ var adapter_categories_default = {
10263
10263
  unit: "MYR",
10264
10264
  range: [0, 1e4],
10265
10265
  description: "Average Revenue Per User (monthly) in Malaysian Ringgit. Coarse spending-capacity proxy."
10266
+ },
10267
+ {
10268
+ name: "telcoPaymentReliabilityTier",
10269
+ type: "string",
10270
+ kind: "enum",
10271
+ description: "Coarse bill-payment reliability bucket, for partners that return a tier label instead of a continuous ratio. Parallel signal to telcoOnTimePaymentRatio24m. Labels are vendor-declared (see the adapter manifest's enumValues); ordering and scoring live in the consumer's per-value mapping."
10272
+ },
10273
+ {
10274
+ name: "telcoTenureTier",
10275
+ type: "string",
10276
+ kind: "enum",
10277
+ description: "Coarse account-age bucket, for partners that return a tenure tier label instead of a continuous month count. Parallel signal to telcoTenureMonths. Labels are vendor-declared (see the adapter manifest's enumValues)."
10278
+ },
10279
+ {
10280
+ name: "telcoDistressTier",
10281
+ type: "string",
10282
+ kind: "enum",
10283
+ description: "Coarse account-distress bucket, for partners that return a distress tier label instead of discrete counts. Parallel signal to telcoSuspensionsCount24m / telcoLateDays24m. Labels are vendor-declared (see the adapter manifest's enumValues)."
10284
+ },
10285
+ {
10286
+ name: "telcoHandsetRiskTier",
10287
+ type: "string",
10288
+ kind: "enum",
10289
+ description: "Coarse handset-financing risk bucket, for partners that return a tier label instead of the discrete handset flags. Parallel signal to telcoHandsetFinancingActive / telcoHandsetFinancingDelinquent. Labels are vendor-declared (see the adapter manifest's enumValues)."
10266
10290
  }
10267
10291
  ]
10268
10292
  },
@@ -11710,6 +11734,7 @@ var VALID_FIELD_TYPES = [
11710
11734
  "boolean",
11711
11735
  "string"
11712
11736
  ];
11737
+ var VALID_FIELD_KINDS = ["enum"];
11713
11738
  function buildCategoryRegistry(raw) {
11714
11739
  if (!raw || typeof raw !== "object") {
11715
11740
  throw new Error("adapter category data: expected an object");
@@ -11777,6 +11802,12 @@ function buildCategoryRegistry(raw) {
11777
11802
  `adapter category data: canonical field "${f.name}" declared by more than one category (${prior.categories.join(" + ")} with ${describeFact(prior.fact)} + ${cat.id} with ${describeFact(f.fact)}) \u2014 field names must be globally unique unless every declaring category attests the same fact`
11778
11803
  );
11779
11804
  }
11805
+ if (prior.kind !== f.kind) {
11806
+ const describeKind = (kind) => kind === void 0 ? "no kind" : `kind "${kind}"`;
11807
+ throw new Error(
11808
+ `adapter category data: canonical field "${f.name}" declared with ${describeKind(prior.kind)} by ${prior.categories.join(" + ")} but ${describeKind(f.kind)} by ${cat.id} \u2014 shared-fact attestations must agree on kind`
11809
+ );
11810
+ }
11780
11811
  }
11781
11812
  if (f.fact !== void 0) {
11782
11813
  for (const [otherName, otherFact] of fieldToFact) {
@@ -11792,6 +11823,23 @@ function buildCategoryRegistry(raw) {
11792
11823
  `adapter category data: field "${f.name}" (${where}) has invalid type "${f.type}"`
11793
11824
  );
11794
11825
  }
11826
+ if (f.kind !== void 0) {
11827
+ if (!VALID_FIELD_KINDS.includes(f.kind)) {
11828
+ throw new Error(
11829
+ `adapter category data: field "${f.name}" (${where}) has invalid kind "${String(f.kind)}"`
11830
+ );
11831
+ }
11832
+ if (f.type !== "string") {
11833
+ throw new Error(
11834
+ `adapter category data: field "${f.name}" (${where}) is kind "enum" but type "${f.type}" \u2014 enum labels are string-normalized, so an enum field must be type "string"`
11835
+ );
11836
+ }
11837
+ if (f.range !== void 0) {
11838
+ throw new Error(
11839
+ `adapter category data: field "${f.name}" (${where}) is kind "enum" but declares a range \u2014 enum labels are unordered; ordering belongs to the consumer, never the data contract`
11840
+ );
11841
+ }
11842
+ }
11795
11843
  if (typeof f.description !== "string" || f.description.length === 0) {
11796
11844
  throw new Error(
11797
11845
  `adapter category data: field "${f.name}" (${where}) needs a non-empty description`
@@ -11810,14 +11858,15 @@ function buildCategoryRegistry(raw) {
11810
11858
  ...f.unit !== void 0 ? { unit: f.unit } : {},
11811
11859
  ...f.range !== void 0 ? { range: Object.freeze([f.range[0], f.range[1]]) } : {},
11812
11860
  description: f.description,
11813
- ...f.fact !== void 0 ? { fact: f.fact } : {}
11861
+ ...f.fact !== void 0 ? { fact: f.fact } : {},
11862
+ ...f.kind !== void 0 ? { kind: f.kind } : {}
11814
11863
  });
11815
11864
  fields.push(spec);
11816
11865
  if (prior) {
11817
11866
  prior.categories.push(cat.id);
11818
11867
  fieldToCategory.delete(f.name);
11819
11868
  } else {
11820
- declarations.set(f.name, { fact: f.fact, categories: [cat.id] });
11869
+ declarations.set(f.name, { fact: f.fact, kind: f.kind, categories: [cat.id] });
11821
11870
  fieldToCategory.set(f.name, cat.id);
11822
11871
  }
11823
11872
  if (f.fact !== void 0) {