@adcp/sdk 14.0.0-rc.37 → 14.0.0-rc.38
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/lib/reporting/content-mismatch.d.mts +126 -0
- package/dist/lib/reporting/content-mismatch.d.ts +126 -0
- package/dist/lib/reporting/content-mismatch.js +90 -0
- package/dist/lib/reporting/content-mismatch.mjs +66 -0
- package/dist/lib/reporting/index.d.mts +3 -1
- package/dist/lib/reporting/index.d.ts +3 -1
- package/dist/lib/reporting/index.js +3 -0
- package/dist/lib/reporting/index.mjs +2 -0
- package/dist/lib/reporting/reconciliation.d.mts +362 -2
- package/dist/lib/reporting/reconciliation.d.ts +362 -2
- package/dist/lib/reporting/reconciliation.js +1051 -7
- package/dist/lib/reporting/reconciliation.mjs +1053 -7
- package/dist/lib/schemas/index.d.mts +22 -1
- package/dist/lib/schemas/index.d.ts +22 -1
- package/dist/lib/schemas/index.js +7 -0
- package/dist/lib/schemas/index.mjs +7 -0
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/validation/schema-loader.d.mts +9 -1
- package/dist/lib/validation/schema-loader.d.ts +9 -1
- package/dist/lib/validation/schema-loader.js +65 -8
- package/dist/lib/validation/schema-loader.mjs +64 -8
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/docs/TYPE-SUMMARY.md +1 -1
- package/docs/guides/EXISTING-PLATFORM.md +45 -1
- package/docs/guides/MEDIA-BUY-3.2-COMPATIBILITY.md +53 -0
- package/docs/guides/REPORTING-RECONCILIATION.md +50 -0
- package/docs/llms.txt +1 -1
- package/docs/migration-14.x-rc-worksheet.md +59 -4
- package/package.json +1 -1
- package/skills/build-seller-agent/SKILL.md +2 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buyer-side `content_mismatch` detection (AdCP 3.2.0-rc.3).
|
|
3
|
+
*
|
|
4
|
+
* `content_mismatch` says: the buyer consumed the exact revision the seller
|
|
5
|
+
* requires, and its content contradicts a fact the accepted configuration
|
|
6
|
+
* generation **already fixed**. Every code is decidable from the obligation,
|
|
7
|
+
* the pinned report definition, and the revision itself — with no reference to
|
|
8
|
+
* either party's own measurement.
|
|
9
|
+
*
|
|
10
|
+
* Two boundaries govern everything below, and both are easy to breach in the
|
|
11
|
+
* permissive direction:
|
|
12
|
+
*
|
|
13
|
+
* 1. **Never a measurement dispute.** A disagreement about *how many
|
|
14
|
+
* impressions the seller counted* belongs to `measurement_terms` and
|
|
15
|
+
* `makegood_policy`. Nothing here compares a delivered number against a
|
|
16
|
+
* buyer-side expectation, and nothing here should be extended to.
|
|
17
|
+
* 2. **Never fire on evidence you do not have.** A false `content_mismatch` is
|
|
18
|
+
* expensive: per `consumer_status_projection` it forces the caller's view to
|
|
19
|
+
* `action_required`, and the seller MUST NOT return the period to healthy
|
|
20
|
+
* while that statement is the current leaf. So four of the six codes are
|
|
21
|
+
* **row-level** predicates that the obligation and revision *metadata*
|
|
22
|
+
* cannot decide, and they stay silent unless the caller supplies what it
|
|
23
|
+
* actually read. Silence is the safe default; a confident wrong answer is
|
|
24
|
+
* not.
|
|
25
|
+
*
|
|
26
|
+
* Which codes need row evidence, and why the metadata alone cannot substitute:
|
|
27
|
+
*
|
|
28
|
+
* | code | spec predicate | decidable from metadata? |
|
|
29
|
+
* | --- | --- | --- |
|
|
30
|
+
* | `scope_media_buy_missing` | a frozen buy is absent from the revision **and not represented by an explicit zero row** | **No.** `reporting-revision.json` defines `media_buy_ids` as the denominator *"inherited from the obligation, including buys with zero rows"*, so comparing the two sets is a tautology against a conformant seller. |
|
|
31
|
+
* | `coverage_short` | revision covers fewer packages than the obligation's frozen `coverage.covered_package_ids` | **Yes.** |
|
|
32
|
+
* | `metric_missing` | a metric named in the pinned definition's `metrics[].name` is absent | **No.** `control_totals` are profile-defined aggregates scoped to `coverage.covered_package_ids`, a different and usually smaller set than the definition's metrics. |
|
|
33
|
+
* | `schema_nonconformant` | rows do not validate against the pinned `schema_uri`/`schema_sha256` | **No.** Requires actually validating rows. |
|
|
34
|
+
* | `currency_mismatch` | a value's unit disagrees with the unit the pinned definition fixed | **Yes.** |
|
|
35
|
+
* | `period_mismatch` | a row time dimension carries values outside the half-open period | **No.** The revision envelope is pinned stable by `slice_identity`; the violation lives in row values. |
|
|
36
|
+
*/
|
|
37
|
+
import type { ReportingControlTotal } from '../types/index.mjs';
|
|
38
|
+
/** Closed set of reasons a consumed revision contradicts the accepted generation. */
|
|
39
|
+
export type ReportingMismatchCodeV1 = 'scope_media_buy_missing' | 'coverage_short' | 'metric_missing' | 'schema_nonconformant' | 'currency_mismatch' | 'period_mismatch';
|
|
40
|
+
/** Facts the accepted configuration generation froze, as the buyer recorded them. */
|
|
41
|
+
export interface ReportingContractFactsV1 {
|
|
42
|
+
/** Frozen media-buy denominator from the obligation. */
|
|
43
|
+
mediaBuyIds?: readonly string[];
|
|
44
|
+
/** Frozen `coverage.covered_package_ids` from the obligation. */
|
|
45
|
+
coveredPackageIds?: readonly string[];
|
|
46
|
+
/** Half-open expected period. */
|
|
47
|
+
period?: {
|
|
48
|
+
start: string;
|
|
49
|
+
end: string;
|
|
50
|
+
};
|
|
51
|
+
/** `metrics[].name` from the pinned report definition. */
|
|
52
|
+
committedMetrics?: readonly string[];
|
|
53
|
+
/** Units the pinned report definition fixed, keyed by metric/control-total name. */
|
|
54
|
+
metricUnits?: Readonly<Record<string, string>>;
|
|
55
|
+
}
|
|
56
|
+
/** The exact revision the buyer read, as the seller published it. */
|
|
57
|
+
export interface ReportingConsumedRevisionV1 {
|
|
58
|
+
reporting_revision_id?: string;
|
|
59
|
+
coverage?: {
|
|
60
|
+
status?: string;
|
|
61
|
+
covered_package_ids?: readonly string[];
|
|
62
|
+
};
|
|
63
|
+
control_totals?: readonly ReportingControlTotal[];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* What the buyer's own reader observed in the revision's rows.
|
|
67
|
+
*
|
|
68
|
+
* Supply only what you actually determined. Every field is optional and each
|
|
69
|
+
* one gates exactly one code: omit it and that code cannot fire, which is the
|
|
70
|
+
* correct outcome for a buyer that did not look.
|
|
71
|
+
*/
|
|
72
|
+
export interface ReportingRowEvidenceV1 {
|
|
73
|
+
/**
|
|
74
|
+
* Media buys represented in the rows, counting a buy carried by an explicit
|
|
75
|
+
* zero row as represented — that zero row is precisely what lets the revision
|
|
76
|
+
* distinguish zero delivery from an omitted buy.
|
|
77
|
+
*/
|
|
78
|
+
representedMediaBuyIds?: readonly string[];
|
|
79
|
+
/** Metric names actually present in the rows. */
|
|
80
|
+
observedMetricNames?: readonly string[];
|
|
81
|
+
/** `false` when the rows failed validation against the pinned profile schema. */
|
|
82
|
+
rowsConformToPinnedSchema?: boolean;
|
|
83
|
+
/** Range of the pinned grain's time dimension across the rows. */
|
|
84
|
+
observedPeriodBounds?: {
|
|
85
|
+
earliest: string;
|
|
86
|
+
latest: string;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export interface ReportingContentMismatchV1 {
|
|
90
|
+
mismatchCode: ReportingMismatchCodeV1;
|
|
91
|
+
/** Non-secret, bounded diagnostic naming the exact contradicted fact. */
|
|
92
|
+
detail: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* First applicable contradiction between a consumed revision and the frozen
|
|
96
|
+
* contract, or `undefined` when nothing decidable is contradicted.
|
|
97
|
+
*
|
|
98
|
+
* **What `reconcileReporting` can reach, and what it cannot.** The reconciler
|
|
99
|
+
* consumes the revision's rows to recompute its binding digest, so it calls
|
|
100
|
+
* this with real row evidence — but it can only derive `observedMetricNames`,
|
|
101
|
+
* and only when the buyer pinned `committedMetrics`. That makes
|
|
102
|
+
* `coverage_short`, `currency_mismatch` and `metric_missing` reachable through
|
|
103
|
+
* the reconcile loop. The remaining three are a **documented limitation**:
|
|
104
|
+
*
|
|
105
|
+
* - `scope_media_buy_missing` needs to know which media buy a row belongs to.
|
|
106
|
+
* - `period_mismatch` needs the time dimension the pinned grain declares.
|
|
107
|
+
* - `schema_nonconformant` needs the rows validated against the pinned schema.
|
|
108
|
+
*
|
|
109
|
+
* All three depend on the reporting profile's own row shape, which is
|
|
110
|
+
* profile-defined and not readable from a generic row object. The SDK does not
|
|
111
|
+
* guess at them, because the cost of being wrong is asymmetric: a false
|
|
112
|
+
* `content_mismatch` pins the caller's view at `action_required` and the seller
|
|
113
|
+
* MUST NOT return the period to healthy while the statement is the current
|
|
114
|
+
* leaf. An adopter whose own reader *does* understand its profile can call this
|
|
115
|
+
* function directly with a fuller `ReportingRowEvidenceV1` and act on the
|
|
116
|
+
* result.
|
|
117
|
+
*
|
|
118
|
+
* **Precedence.** The spec pins exactly one ordering rule: a metric that is
|
|
119
|
+
* simply absent uses `metric_missing` even when the pinned schema declares it
|
|
120
|
+
* required, so `metric_missing` is evaluated before `schema_nonconformant`.
|
|
121
|
+
* The remaining order is this SDK's, chosen so the most structural fact wins —
|
|
122
|
+
* a revision missing a whole media buy reports that, not whichever metric
|
|
123
|
+
* happened to go missing with it — and is stable so the code does not flap
|
|
124
|
+
* between reads of the same bytes.
|
|
125
|
+
*/
|
|
126
|
+
export declare function detectReportingContentMismatch(facts: ReportingContractFactsV1, revision: ReportingConsumedRevisionV1, rows?: ReportingRowEvidenceV1): ReportingContentMismatchV1 | undefined;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buyer-side `content_mismatch` detection (AdCP 3.2.0-rc.3).
|
|
3
|
+
*
|
|
4
|
+
* `content_mismatch` says: the buyer consumed the exact revision the seller
|
|
5
|
+
* requires, and its content contradicts a fact the accepted configuration
|
|
6
|
+
* generation **already fixed**. Every code is decidable from the obligation,
|
|
7
|
+
* the pinned report definition, and the revision itself — with no reference to
|
|
8
|
+
* either party's own measurement.
|
|
9
|
+
*
|
|
10
|
+
* Two boundaries govern everything below, and both are easy to breach in the
|
|
11
|
+
* permissive direction:
|
|
12
|
+
*
|
|
13
|
+
* 1. **Never a measurement dispute.** A disagreement about *how many
|
|
14
|
+
* impressions the seller counted* belongs to `measurement_terms` and
|
|
15
|
+
* `makegood_policy`. Nothing here compares a delivered number against a
|
|
16
|
+
* buyer-side expectation, and nothing here should be extended to.
|
|
17
|
+
* 2. **Never fire on evidence you do not have.** A false `content_mismatch` is
|
|
18
|
+
* expensive: per `consumer_status_projection` it forces the caller's view to
|
|
19
|
+
* `action_required`, and the seller MUST NOT return the period to healthy
|
|
20
|
+
* while that statement is the current leaf. So four of the six codes are
|
|
21
|
+
* **row-level** predicates that the obligation and revision *metadata*
|
|
22
|
+
* cannot decide, and they stay silent unless the caller supplies what it
|
|
23
|
+
* actually read. Silence is the safe default; a confident wrong answer is
|
|
24
|
+
* not.
|
|
25
|
+
*
|
|
26
|
+
* Which codes need row evidence, and why the metadata alone cannot substitute:
|
|
27
|
+
*
|
|
28
|
+
* | code | spec predicate | decidable from metadata? |
|
|
29
|
+
* | --- | --- | --- |
|
|
30
|
+
* | `scope_media_buy_missing` | a frozen buy is absent from the revision **and not represented by an explicit zero row** | **No.** `reporting-revision.json` defines `media_buy_ids` as the denominator *"inherited from the obligation, including buys with zero rows"*, so comparing the two sets is a tautology against a conformant seller. |
|
|
31
|
+
* | `coverage_short` | revision covers fewer packages than the obligation's frozen `coverage.covered_package_ids` | **Yes.** |
|
|
32
|
+
* | `metric_missing` | a metric named in the pinned definition's `metrics[].name` is absent | **No.** `control_totals` are profile-defined aggregates scoped to `coverage.covered_package_ids`, a different and usually smaller set than the definition's metrics. |
|
|
33
|
+
* | `schema_nonconformant` | rows do not validate against the pinned `schema_uri`/`schema_sha256` | **No.** Requires actually validating rows. |
|
|
34
|
+
* | `currency_mismatch` | a value's unit disagrees with the unit the pinned definition fixed | **Yes.** |
|
|
35
|
+
* | `period_mismatch` | a row time dimension carries values outside the half-open period | **No.** The revision envelope is pinned stable by `slice_identity`; the violation lives in row values. |
|
|
36
|
+
*/
|
|
37
|
+
import type { ReportingControlTotal } from '../types';
|
|
38
|
+
/** Closed set of reasons a consumed revision contradicts the accepted generation. */
|
|
39
|
+
export type ReportingMismatchCodeV1 = 'scope_media_buy_missing' | 'coverage_short' | 'metric_missing' | 'schema_nonconformant' | 'currency_mismatch' | 'period_mismatch';
|
|
40
|
+
/** Facts the accepted configuration generation froze, as the buyer recorded them. */
|
|
41
|
+
export interface ReportingContractFactsV1 {
|
|
42
|
+
/** Frozen media-buy denominator from the obligation. */
|
|
43
|
+
mediaBuyIds?: readonly string[];
|
|
44
|
+
/** Frozen `coverage.covered_package_ids` from the obligation. */
|
|
45
|
+
coveredPackageIds?: readonly string[];
|
|
46
|
+
/** Half-open expected period. */
|
|
47
|
+
period?: {
|
|
48
|
+
start: string;
|
|
49
|
+
end: string;
|
|
50
|
+
};
|
|
51
|
+
/** `metrics[].name` from the pinned report definition. */
|
|
52
|
+
committedMetrics?: readonly string[];
|
|
53
|
+
/** Units the pinned report definition fixed, keyed by metric/control-total name. */
|
|
54
|
+
metricUnits?: Readonly<Record<string, string>>;
|
|
55
|
+
}
|
|
56
|
+
/** The exact revision the buyer read, as the seller published it. */
|
|
57
|
+
export interface ReportingConsumedRevisionV1 {
|
|
58
|
+
reporting_revision_id?: string;
|
|
59
|
+
coverage?: {
|
|
60
|
+
status?: string;
|
|
61
|
+
covered_package_ids?: readonly string[];
|
|
62
|
+
};
|
|
63
|
+
control_totals?: readonly ReportingControlTotal[];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* What the buyer's own reader observed in the revision's rows.
|
|
67
|
+
*
|
|
68
|
+
* Supply only what you actually determined. Every field is optional and each
|
|
69
|
+
* one gates exactly one code: omit it and that code cannot fire, which is the
|
|
70
|
+
* correct outcome for a buyer that did not look.
|
|
71
|
+
*/
|
|
72
|
+
export interface ReportingRowEvidenceV1 {
|
|
73
|
+
/**
|
|
74
|
+
* Media buys represented in the rows, counting a buy carried by an explicit
|
|
75
|
+
* zero row as represented — that zero row is precisely what lets the revision
|
|
76
|
+
* distinguish zero delivery from an omitted buy.
|
|
77
|
+
*/
|
|
78
|
+
representedMediaBuyIds?: readonly string[];
|
|
79
|
+
/** Metric names actually present in the rows. */
|
|
80
|
+
observedMetricNames?: readonly string[];
|
|
81
|
+
/** `false` when the rows failed validation against the pinned profile schema. */
|
|
82
|
+
rowsConformToPinnedSchema?: boolean;
|
|
83
|
+
/** Range of the pinned grain's time dimension across the rows. */
|
|
84
|
+
observedPeriodBounds?: {
|
|
85
|
+
earliest: string;
|
|
86
|
+
latest: string;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export interface ReportingContentMismatchV1 {
|
|
90
|
+
mismatchCode: ReportingMismatchCodeV1;
|
|
91
|
+
/** Non-secret, bounded diagnostic naming the exact contradicted fact. */
|
|
92
|
+
detail: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* First applicable contradiction between a consumed revision and the frozen
|
|
96
|
+
* contract, or `undefined` when nothing decidable is contradicted.
|
|
97
|
+
*
|
|
98
|
+
* **What `reconcileReporting` can reach, and what it cannot.** The reconciler
|
|
99
|
+
* consumes the revision's rows to recompute its binding digest, so it calls
|
|
100
|
+
* this with real row evidence — but it can only derive `observedMetricNames`,
|
|
101
|
+
* and only when the buyer pinned `committedMetrics`. That makes
|
|
102
|
+
* `coverage_short`, `currency_mismatch` and `metric_missing` reachable through
|
|
103
|
+
* the reconcile loop. The remaining three are a **documented limitation**:
|
|
104
|
+
*
|
|
105
|
+
* - `scope_media_buy_missing` needs to know which media buy a row belongs to.
|
|
106
|
+
* - `period_mismatch` needs the time dimension the pinned grain declares.
|
|
107
|
+
* - `schema_nonconformant` needs the rows validated against the pinned schema.
|
|
108
|
+
*
|
|
109
|
+
* All three depend on the reporting profile's own row shape, which is
|
|
110
|
+
* profile-defined and not readable from a generic row object. The SDK does not
|
|
111
|
+
* guess at them, because the cost of being wrong is asymmetric: a false
|
|
112
|
+
* `content_mismatch` pins the caller's view at `action_required` and the seller
|
|
113
|
+
* MUST NOT return the period to healthy while the statement is the current
|
|
114
|
+
* leaf. An adopter whose own reader *does* understand its profile can call this
|
|
115
|
+
* function directly with a fuller `ReportingRowEvidenceV1` and act on the
|
|
116
|
+
* result.
|
|
117
|
+
*
|
|
118
|
+
* **Precedence.** The spec pins exactly one ordering rule: a metric that is
|
|
119
|
+
* simply absent uses `metric_missing` even when the pinned schema declares it
|
|
120
|
+
* required, so `metric_missing` is evaluated before `schema_nonconformant`.
|
|
121
|
+
* The remaining order is this SDK's, chosen so the most structural fact wins —
|
|
122
|
+
* a revision missing a whole media buy reports that, not whichever metric
|
|
123
|
+
* happened to go missing with it — and is stable so the code does not flap
|
|
124
|
+
* between reads of the same bytes.
|
|
125
|
+
*/
|
|
126
|
+
export declare function detectReportingContentMismatch(facts: ReportingContractFactsV1, revision: ReportingConsumedRevisionV1, rows?: ReportingRowEvidenceV1): ReportingContentMismatchV1 | undefined;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var content_mismatch_exports = {};
|
|
20
|
+
__export(content_mismatch_exports, {
|
|
21
|
+
detectReportingContentMismatch: () => detectReportingContentMismatch
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(content_mismatch_exports);
|
|
24
|
+
function detectReportingContentMismatch(facts, revision, rows = {}) {
|
|
25
|
+
if (rows.representedMediaBuyIds) {
|
|
26
|
+
const omitted = (facts.mediaBuyIds ?? []).find((id) => !rows.representedMediaBuyIds.includes(id));
|
|
27
|
+
if (omitted !== void 0) {
|
|
28
|
+
return {
|
|
29
|
+
mismatchCode: "scope_media_buy_missing",
|
|
30
|
+
detail: `media_buy_id ${bounded(omitted)} is in the obligation denominator but has no rows and no explicit zero row`
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const missingPackage = (facts.coveredPackageIds ?? []).find(
|
|
35
|
+
(id) => !(revision.coverage?.covered_package_ids ?? []).includes(id)
|
|
36
|
+
);
|
|
37
|
+
if (missingPackage !== void 0) {
|
|
38
|
+
return {
|
|
39
|
+
mismatchCode: "coverage_short",
|
|
40
|
+
detail: `package_id ${bounded(missingPackage)} is in the obligation's frozen coverage but absent from the revision`
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (facts.period && rows.observedPeriodBounds) {
|
|
44
|
+
const start = Date.parse(facts.period.start);
|
|
45
|
+
const end = Date.parse(facts.period.end);
|
|
46
|
+
const earliest = Date.parse(rows.observedPeriodBounds.earliest);
|
|
47
|
+
const latest = Date.parse(rows.observedPeriodBounds.latest);
|
|
48
|
+
if ([start, end, earliest, latest].every(Number.isFinite) && (earliest < start || latest >= end)) {
|
|
49
|
+
return {
|
|
50
|
+
mismatchCode: "period_mismatch",
|
|
51
|
+
detail: `rows carry time-dimension values from ${bounded(rows.observedPeriodBounds.earliest)} to ${bounded(rows.observedPeriodBounds.latest)}, outside the obligation's half-open period`
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (rows.observedMetricNames) {
|
|
56
|
+
const missingMetric = (facts.committedMetrics ?? []).find((name) => !rows.observedMetricNames.includes(name));
|
|
57
|
+
if (missingMetric !== void 0) {
|
|
58
|
+
return {
|
|
59
|
+
mismatchCode: "metric_missing",
|
|
60
|
+
detail: `metric ${bounded(missingMetric)} is promised by the pinned report definition but absent from the revision`
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const totalsByName = new Map(
|
|
65
|
+
(revision.control_totals ?? []).map((total) => [total.name, total])
|
|
66
|
+
);
|
|
67
|
+
for (const [name, expectedUnit] of Object.entries(facts.metricUnits ?? {})) {
|
|
68
|
+
const total = totalsByName.get(name);
|
|
69
|
+
if (total && total.unit !== void 0 && total.unit !== expectedUnit) {
|
|
70
|
+
return {
|
|
71
|
+
mismatchCode: "currency_mismatch",
|
|
72
|
+
detail: `metric ${bounded(name)} reports unit ${bounded(total.unit)} where the pinned definition fixed ${bounded(expectedUnit)}`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (rows.rowsConformToPinnedSchema === false) {
|
|
77
|
+
return {
|
|
78
|
+
mismatchCode: "schema_nonconformant",
|
|
79
|
+
detail: "rows do not validate against the reporting profile schema pinned by the accepted generation"
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
function bounded(value) {
|
|
85
|
+
return value.replace(/[\r\n\t]+/g, " ").slice(0, 64);
|
|
86
|
+
}
|
|
87
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
88
|
+
0 && (module.exports = {
|
|
89
|
+
detectReportingContentMismatch
|
|
90
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
function detectReportingContentMismatch(facts, revision, rows = {}) {
|
|
2
|
+
if (rows.representedMediaBuyIds) {
|
|
3
|
+
const omitted = (facts.mediaBuyIds ?? []).find((id) => !rows.representedMediaBuyIds.includes(id));
|
|
4
|
+
if (omitted !== void 0) {
|
|
5
|
+
return {
|
|
6
|
+
mismatchCode: "scope_media_buy_missing",
|
|
7
|
+
detail: `media_buy_id ${bounded(omitted)} is in the obligation denominator but has no rows and no explicit zero row`
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const missingPackage = (facts.coveredPackageIds ?? []).find(
|
|
12
|
+
(id) => !(revision.coverage?.covered_package_ids ?? []).includes(id)
|
|
13
|
+
);
|
|
14
|
+
if (missingPackage !== void 0) {
|
|
15
|
+
return {
|
|
16
|
+
mismatchCode: "coverage_short",
|
|
17
|
+
detail: `package_id ${bounded(missingPackage)} is in the obligation's frozen coverage but absent from the revision`
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
if (facts.period && rows.observedPeriodBounds) {
|
|
21
|
+
const start = Date.parse(facts.period.start);
|
|
22
|
+
const end = Date.parse(facts.period.end);
|
|
23
|
+
const earliest = Date.parse(rows.observedPeriodBounds.earliest);
|
|
24
|
+
const latest = Date.parse(rows.observedPeriodBounds.latest);
|
|
25
|
+
if ([start, end, earliest, latest].every(Number.isFinite) && (earliest < start || latest >= end)) {
|
|
26
|
+
return {
|
|
27
|
+
mismatchCode: "period_mismatch",
|
|
28
|
+
detail: `rows carry time-dimension values from ${bounded(rows.observedPeriodBounds.earliest)} to ${bounded(rows.observedPeriodBounds.latest)}, outside the obligation's half-open period`
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (rows.observedMetricNames) {
|
|
33
|
+
const missingMetric = (facts.committedMetrics ?? []).find((name) => !rows.observedMetricNames.includes(name));
|
|
34
|
+
if (missingMetric !== void 0) {
|
|
35
|
+
return {
|
|
36
|
+
mismatchCode: "metric_missing",
|
|
37
|
+
detail: `metric ${bounded(missingMetric)} is promised by the pinned report definition but absent from the revision`
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const totalsByName = new Map(
|
|
42
|
+
(revision.control_totals ?? []).map((total) => [total.name, total])
|
|
43
|
+
);
|
|
44
|
+
for (const [name, expectedUnit] of Object.entries(facts.metricUnits ?? {})) {
|
|
45
|
+
const total = totalsByName.get(name);
|
|
46
|
+
if (total && total.unit !== void 0 && total.unit !== expectedUnit) {
|
|
47
|
+
return {
|
|
48
|
+
mismatchCode: "currency_mismatch",
|
|
49
|
+
detail: `metric ${bounded(name)} reports unit ${bounded(total.unit)} where the pinned definition fixed ${bounded(expectedUnit)}`
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (rows.rowsConformToPinnedSchema === false) {
|
|
54
|
+
return {
|
|
55
|
+
mismatchCode: "schema_nonconformant",
|
|
56
|
+
detail: "rows do not validate against the reporting profile schema pinned by the accepted generation"
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
function bounded(value) {
|
|
62
|
+
return value.replace(/[\r\n\t]+/g, " ").slice(0, 64);
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
detectReportingContentMismatch
|
|
66
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
export { detectReportingContentMismatch } from './content-mismatch.mjs';
|
|
2
|
+
export type { ReportingConsumedRevisionV1, ReportingContentMismatchV1, ReportingContractFactsV1, ReportingMismatchCodeV1, ReportingRowEvidenceV1, } from './content-mismatch.mjs';
|
|
1
3
|
export { ReportingReconciliationError, buildReportingReceipt, evaluateReportingLedger, isReportingCoverageEvidence, loadReportingLedger, reconcileReporting, } from './reconciliation.mjs';
|
|
2
4
|
export { ReportingInspectionError, createHttpsReportingResourceReader, createReportingManifestInspector, } from './inspection.mjs';
|
|
3
|
-
export type { ExpectedReportingPeriod, ExpectedReportingCoverage, ObligationReconciliation, ReconcileReportingOptions, ReportingCheckpoint, ReportingCheckpointKey, ReportingCheckpointStore, ReportingCanonicalDigestEvidence, ReportingCoverageEvidence, ReportingCoverageLimitation, ReportingInspectionContext, ReportingLedger, ReportingLedgerLimits, ReportingObservation, ReportingReconciliationClient, ReportingReconciliationResult, } from './reconciliation.mjs';
|
|
5
|
+
export type { ExpectedReportingPeriod, ExpectedReportingCoverage, ObligationReconciliation, ReconcileReportingOptions, ReportingCheckpoint, ReportingCheckpointKey, ReportingCheckpointStore, ReportingPendingConsumerStatus, ReportingPendingConsumerStatusKey, ReportingPendingConsumerStatusStore, ReportingCanonicalDigestEvidence, ReportingCoverageEvidence, ReportingCoverageLimitation, ReportingInspectionContext, ReportingLedger, ReportingLedgerLimits, ReportingObservation, ReportingConsumerStatusPlanV1, ReportingEscalationV1, ReportingReconciliationClient, ReportingReconciliationResult, } from './reconciliation.mjs';
|
|
4
6
|
export type { HttpsReportingResourceReaderOptions, ReportingCompressionDecoder, ReportingControlTotalCalculator, ReportingCredentialProvider, ReportingDecodedFileContext, ReportingFormatDecoder, ReportingHttpCredentials, ReportingInspectionErrorCode, ReportingManifestInspectorOptions, ReportingResourceReadRequest, ReportingResourceReadResult, ReportingResourceReadRole, ReportingResourceReader, } from './inspection.mjs';
|
|
5
7
|
export { assertSupportedReportingAuthoritativeParty, UnsupportedReportingFeatureError } from './ledger/producer.mjs';
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
export { detectReportingContentMismatch } from './content-mismatch';
|
|
2
|
+
export type { ReportingConsumedRevisionV1, ReportingContentMismatchV1, ReportingContractFactsV1, ReportingMismatchCodeV1, ReportingRowEvidenceV1, } from './content-mismatch';
|
|
1
3
|
export { ReportingReconciliationError, buildReportingReceipt, evaluateReportingLedger, isReportingCoverageEvidence, loadReportingLedger, reconcileReporting, } from './reconciliation';
|
|
2
4
|
export { ReportingInspectionError, createHttpsReportingResourceReader, createReportingManifestInspector, } from './inspection';
|
|
3
|
-
export type { ExpectedReportingPeriod, ExpectedReportingCoverage, ObligationReconciliation, ReconcileReportingOptions, ReportingCheckpoint, ReportingCheckpointKey, ReportingCheckpointStore, ReportingCanonicalDigestEvidence, ReportingCoverageEvidence, ReportingCoverageLimitation, ReportingInspectionContext, ReportingLedger, ReportingLedgerLimits, ReportingObservation, ReportingReconciliationClient, ReportingReconciliationResult, } from './reconciliation';
|
|
5
|
+
export type { ExpectedReportingPeriod, ExpectedReportingCoverage, ObligationReconciliation, ReconcileReportingOptions, ReportingCheckpoint, ReportingCheckpointKey, ReportingCheckpointStore, ReportingPendingConsumerStatus, ReportingPendingConsumerStatusKey, ReportingPendingConsumerStatusStore, ReportingCanonicalDigestEvidence, ReportingCoverageEvidence, ReportingCoverageLimitation, ReportingInspectionContext, ReportingLedger, ReportingLedgerLimits, ReportingObservation, ReportingConsumerStatusPlanV1, ReportingEscalationV1, ReportingReconciliationClient, ReportingReconciliationResult, } from './reconciliation';
|
|
4
6
|
export type { HttpsReportingResourceReaderOptions, ReportingCompressionDecoder, ReportingControlTotalCalculator, ReportingCredentialProvider, ReportingDecodedFileContext, ReportingFormatDecoder, ReportingHttpCredentials, ReportingInspectionErrorCode, ReportingManifestInspectorOptions, ReportingResourceReadRequest, ReportingResourceReadResult, ReportingResourceReadRole, ReportingResourceReader, } from './inspection';
|
|
5
7
|
export { assertSupportedReportingAuthoritativeParty, UnsupportedReportingFeatureError } from './ledger/producer';
|
|
@@ -25,12 +25,14 @@ __export(reporting_exports, {
|
|
|
25
25
|
buildReportingReceipt: () => import_reconciliation.buildReportingReceipt,
|
|
26
26
|
createHttpsReportingResourceReader: () => import_inspection.createHttpsReportingResourceReader,
|
|
27
27
|
createReportingManifestInspector: () => import_inspection.createReportingManifestInspector,
|
|
28
|
+
detectReportingContentMismatch: () => import_content_mismatch.detectReportingContentMismatch,
|
|
28
29
|
evaluateReportingLedger: () => import_reconciliation.evaluateReportingLedger,
|
|
29
30
|
isReportingCoverageEvidence: () => import_reconciliation.isReportingCoverageEvidence,
|
|
30
31
|
loadReportingLedger: () => import_reconciliation.loadReportingLedger,
|
|
31
32
|
reconcileReporting: () => import_reconciliation.reconcileReporting
|
|
32
33
|
});
|
|
33
34
|
module.exports = __toCommonJS(reporting_exports);
|
|
35
|
+
var import_content_mismatch = require('./content-mismatch.js');
|
|
34
36
|
var import_reconciliation = require('./reconciliation.js');
|
|
35
37
|
var import_inspection = require('./inspection.js');
|
|
36
38
|
var import_producer = require('./ledger/producer.js');
|
|
@@ -43,6 +45,7 @@ var import_producer = require('./ledger/producer.js');
|
|
|
43
45
|
buildReportingReceipt,
|
|
44
46
|
createHttpsReportingResourceReader,
|
|
45
47
|
createReportingManifestInspector,
|
|
48
|
+
detectReportingContentMismatch,
|
|
46
49
|
evaluateReportingLedger,
|
|
47
50
|
isReportingCoverageEvidence,
|
|
48
51
|
loadReportingLedger,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { detectReportingContentMismatch } from "./content-mismatch.mjs";
|
|
1
2
|
import {
|
|
2
3
|
ReportingReconciliationError,
|
|
3
4
|
buildReportingReceipt,
|
|
@@ -20,6 +21,7 @@ export {
|
|
|
20
21
|
buildReportingReceipt,
|
|
21
22
|
createHttpsReportingResourceReader,
|
|
22
23
|
createReportingManifestInspector,
|
|
24
|
+
detectReportingContentMismatch,
|
|
23
25
|
evaluateReportingLedger,
|
|
24
26
|
isReportingCoverageEvidence,
|
|
25
27
|
loadReportingLedger,
|