plumb-line-provenance 0.2.0 → 0.3.1
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/audit.mjs +6 -1
- package/marked.mjs +8 -4
- package/package.json +5 -2
- package/provenance.mjs +36 -12
package/audit.mjs
CHANGED
|
@@ -20,9 +20,14 @@ export function auditMeta(meta) {
|
|
|
20
20
|
);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
// An unknown confidence on a step is laundering, not "no signal": treat it as
|
|
24
|
+
// the `none` floor (mirroring weakestConfidence), so audit is never laxer than
|
|
25
|
+
// the combination law. A step that records *no* confidence is still skipped —
|
|
26
|
+
// absence is genuinely unrankable and must not manufacture a false over-claim.
|
|
23
27
|
const lineageConfidences = lineage
|
|
24
28
|
.map((s) => s?.confidence)
|
|
25
|
-
.filter((c) =>
|
|
29
|
+
.filter((c) => c != null)
|
|
30
|
+
.map((c) => (CONFIDENCE.includes(c) ? c : "none"));
|
|
26
31
|
if (lineageConfidences.length > 0) {
|
|
27
32
|
const weakest = weakestConfidence(...lineageConfidences);
|
|
28
33
|
if (CONFIDENCE.indexOf(meta.confidence) > CONFIDENCE.indexOf(weakest)) {
|
package/marked.mjs
CHANGED
|
@@ -18,7 +18,7 @@ const META_KEYS = [
|
|
|
18
18
|
const OVERRIDE_KEYS = ["source", "confidence", "confidenceScore", "basis", "adapter"];
|
|
19
19
|
|
|
20
20
|
export function mark(value, metaInput = {}) {
|
|
21
|
-
return { value, ...makeMeta(metaInput) };
|
|
21
|
+
return Object.freeze({ value, ...makeMeta(metaInput) });
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export function unwrap(marked) {
|
|
@@ -39,11 +39,15 @@ export function derive(inputs, fn, metaOverride = {}) {
|
|
|
39
39
|
for (const key of OVERRIDE_KEYS) {
|
|
40
40
|
if (key in metaOverride) safeOverride[key] = metaOverride[key];
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
// Route the override through makeMeta so derive is never weaker than the
|
|
43
|
+
// constructor: an out-of-range confidenceScore (or unrankable weakestSource)
|
|
44
|
+
// is dropped by the same validation, not stored raw. derivedFromMock is
|
|
45
|
+
// force-OR'd *before* the call, so taint still cannot be cleared (the one law).
|
|
46
|
+
const merged = makeMeta({
|
|
43
47
|
...combined,
|
|
44
48
|
...safeOverride,
|
|
45
49
|
derivedFromMock:
|
|
46
50
|
combined.derivedFromMock || Boolean(metaOverride.derivedFromMock),
|
|
47
|
-
};
|
|
48
|
-
return { value, ...merged };
|
|
51
|
+
});
|
|
52
|
+
return Object.freeze({ value, ...merged });
|
|
49
53
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plumb-line-provenance",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Conservative provenance/confidence/lineage envelope with a taint-propagation combination law. Mock or low-confidence data cannot launder itself clean.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|
|
@@ -28,6 +28,9 @@
|
|
|
28
28
|
],
|
|
29
29
|
"author": "Aoife Okonedo Martin",
|
|
30
30
|
"license": "Apache-2.0",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/effythealien/plumb-line/issues"
|
|
33
|
+
},
|
|
31
34
|
"homepage": "https://github.com/effythealien/plumb-line/tree/main/primitives",
|
|
32
35
|
"repository": {
|
|
33
36
|
"type": "git",
|
|
@@ -38,6 +41,6 @@
|
|
|
38
41
|
"node": ">=16"
|
|
39
42
|
},
|
|
40
43
|
"devDependencies": {
|
|
41
|
-
"vitest": "^
|
|
44
|
+
"vitest": "^4.1.9"
|
|
42
45
|
}
|
|
43
46
|
}
|
package/provenance.mjs
CHANGED
|
@@ -36,7 +36,15 @@ export function makeMeta({
|
|
|
36
36
|
derivedFromMock === undefined
|
|
37
37
|
? source === "mock"
|
|
38
38
|
: Boolean(derivedFromMock),
|
|
39
|
-
|
|
39
|
+
// Each meta owns a *frozen copy* of its lineage. Steps are cloned then
|
|
40
|
+
// frozen so (a) an envelope's recorded history can't be rewritten in place,
|
|
41
|
+
// and (b) a step shared across parent/child metas can't leak a mutation from
|
|
42
|
+
// one into the other — the audit trail an auditMeta() trusts stays intact.
|
|
43
|
+
lineage: Object.freeze(
|
|
44
|
+
(Array.isArray(lineage) ? lineage : []).map((s) =>
|
|
45
|
+
s && typeof s === "object" ? Object.freeze({ ...s }) : s,
|
|
46
|
+
),
|
|
47
|
+
),
|
|
40
48
|
};
|
|
41
49
|
// Optional numeric confidence — a finer-grained companion to the ordinal
|
|
42
50
|
// `confidence`, never a replacement. Stored only when it is a valid score.
|
|
@@ -46,7 +54,7 @@ export function makeMeta({
|
|
|
46
54
|
if (STATUS.includes(weakestSource)) meta.weakestSource = weakestSource;
|
|
47
55
|
if (basis !== undefined) meta.basis = basis;
|
|
48
56
|
if (adapter !== undefined) meta.adapter = adapter;
|
|
49
|
-
return meta;
|
|
57
|
+
return Object.freeze(meta);
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
export function weakestConfidence(...levels) {
|
|
@@ -82,16 +90,24 @@ export function combineConfidenceScore(scores) {
|
|
|
82
90
|
return Math.min(...scores);
|
|
83
91
|
}
|
|
84
92
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
function nextStepId() {
|
|
90
|
-
__stepCounter += 1;
|
|
91
|
-
return `step-${__stepCounter}`;
|
|
92
|
-
}
|
|
93
|
+
// Deprecated no-op, kept for import compatibility. Step IDs are now assigned by
|
|
94
|
+
// a counter local to each combineProvenance call (see below), so there is no
|
|
95
|
+
// shared state to reset between runs. Safe to delete from call sites.
|
|
96
|
+
export function __resetStepCounter() {}
|
|
93
97
|
|
|
94
98
|
export function combineProvenance(...metas) {
|
|
99
|
+
// A value combined from no inputs is derived from nothing — honestly
|
|
100
|
+
// 'unavailable', not 'derived'. Returning 'derived' with an empty lineage
|
|
101
|
+
// would contradict auditMeta's "derived value has no lineage" check
|
|
102
|
+
// (SPEC §3 vs §5). See #25.
|
|
103
|
+
if (metas.length === 0) {
|
|
104
|
+
return makeMeta({
|
|
105
|
+
source: "unavailable",
|
|
106
|
+
confidence: "none",
|
|
107
|
+
derivedFromMock: false,
|
|
108
|
+
lineage: [],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
95
111
|
const derivedFromMock = metas.some((m) => taints(m));
|
|
96
112
|
const confidence = weakestConfidence(...metas.map((m) => m?.confidence));
|
|
97
113
|
const confidenceScore = combineConfidenceScore(
|
|
@@ -102,7 +118,6 @@ export function combineProvenance(...metas) {
|
|
|
102
118
|
);
|
|
103
119
|
const inputSteps = metas.map((m) => {
|
|
104
120
|
const step = {
|
|
105
|
-
id: nextStepId(),
|
|
106
121
|
of: "input",
|
|
107
122
|
source: m?.source,
|
|
108
123
|
confidence: m?.confidence,
|
|
@@ -113,7 +128,16 @@ export function combineProvenance(...metas) {
|
|
|
113
128
|
if (isScore(m?.confidenceScore)) step.confidenceScore = m.confidenceScore;
|
|
114
129
|
return step;
|
|
115
130
|
});
|
|
116
|
-
|
|
131
|
+
// Renumber the *entire* output lineage from a combine-local counter, so step
|
|
132
|
+
// IDs are unique-within-output (SPEC §4) for every input shape — two
|
|
133
|
+
// independently-built inputs each start at step-1, so seeding past the prior
|
|
134
|
+
// length alone wouldn't stop their inherited steps from colliding. No
|
|
135
|
+
// module-level state means concurrent combines can't collide either. IDs are
|
|
136
|
+
// thus a pure function of output structure, not creation order. See #23.
|
|
137
|
+
const lineage = [...priorLineage, ...inputSteps].map((s, i) => ({
|
|
138
|
+
...s,
|
|
139
|
+
id: `step-${i + 1}`,
|
|
140
|
+
}));
|
|
117
141
|
return makeMeta({
|
|
118
142
|
source: "derived",
|
|
119
143
|
confidence,
|