@dsh-enhanced/plugin-control-plane 0.1.65 → 0.1.66
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 +81 -5
- package/lib/attestation.d.ts.map +1 -1
- package/lib/attestation.js +33 -1
- package/lib/attestation.js.map +1 -1
- package/lib/creation-capability-empty-handoff.d.ts +57 -0
- package/lib/creation-capability-empty-handoff.d.ts.map +1 -0
- package/lib/creation-capability-empty-handoff.js +147 -0
- package/lib/creation-capability-empty-handoff.js.map +1 -0
- package/lib/creation-capability-execution.d.ts +9 -0
- package/lib/creation-capability-execution.d.ts.map +1 -0
- package/lib/creation-capability-execution.js +70 -0
- package/lib/creation-capability-execution.js.map +1 -0
- package/lib/creation-capability-feedback.d.ts +58 -1
- package/lib/creation-capability-feedback.d.ts.map +1 -1
- package/lib/creation-capability-feedback.js +532 -50
- package/lib/creation-capability-feedback.js.map +1 -1
- package/lib/creation-capability-handoff-common.d.ts +60 -0
- package/lib/creation-capability-handoff-common.d.ts.map +1 -0
- package/lib/creation-capability-handoff-common.js +195 -0
- package/lib/creation-capability-handoff-common.js.map +1 -0
- package/lib/creation-capability-journal.d.ts +19 -0
- package/lib/creation-capability-journal.d.ts.map +1 -1
- package/lib/creation-capability-journal.js +62 -21
- package/lib/creation-capability-journal.js.map +1 -1
- package/lib/creation-capability-runtime.d.ts +18 -0
- package/lib/creation-capability-runtime.d.ts.map +1 -1
- package/lib/creation-capability-runtime.js +76 -18
- package/lib/creation-capability-runtime.js.map +1 -1
- package/lib/creation-capability-sealed-audit.d.ts +19 -0
- package/lib/creation-capability-sealed-audit.d.ts.map +1 -0
- package/lib/creation-capability-sealed-audit.js +114 -0
- package/lib/creation-capability-sealed-audit.js.map +1 -0
- package/lib/creation-capability-sealed-handoff.d.ts +160 -0
- package/lib/creation-capability-sealed-handoff.d.ts.map +1 -0
- package/lib/creation-capability-sealed-handoff.js +695 -0
- package/lib/creation-capability-sealed-handoff.js.map +1 -0
- package/lib/creation-capability-trend-snapshot.d.ts +70 -0
- package/lib/creation-capability-trend-snapshot.d.ts.map +1 -0
- package/lib/creation-capability-trend-snapshot.js +117 -0
- package/lib/creation-capability-trend-snapshot.js.map +1 -0
- package/lib/creation-capability-types.d.ts +254 -4
- package/lib/creation-capability-types.d.ts.map +1 -1
- package/lib/in-process-reload.d.ts +191 -0
- package/lib/in-process-reload.d.ts.map +1 -0
- package/lib/in-process-reload.js +284 -0
- package/lib/in-process-reload.js.map +1 -0
- package/lib/index.d.ts +12 -4
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +8 -2
- package/lib/index.js.map +1 -1
- package/lib/linux-source-build.d.ts +16 -0
- package/lib/linux-source-build.d.ts.map +1 -0
- package/lib/linux-source-build.js +268 -0
- package/lib/linux-source-build.js.map +1 -0
- package/lib/linux-source-preflight.d.ts +31 -0
- package/lib/linux-source-preflight.d.ts.map +1 -0
- package/lib/linux-source-preflight.js +101 -0
- package/lib/linux-source-preflight.js.map +1 -0
- package/lib/live-qualification-runtime.d.ts +6 -2
- package/lib/live-qualification-runtime.d.ts.map +1 -1
- package/lib/live-qualification-runtime.js +65 -11
- package/lib/live-qualification-runtime.js.map +1 -1
- package/lib/owner-task-gap-types.d.ts +23 -0
- package/lib/owner-task-gap-types.d.ts.map +1 -1
- package/lib/owner-task-gaps.d.ts +4 -0
- package/lib/owner-task-gaps.d.ts.map +1 -1
- package/lib/owner-task-gaps.js +35 -1
- package/lib/owner-task-gaps.js.map +1 -1
- package/lib/scanner-owner-runtime.d.ts +34 -0
- package/lib/scanner-owner-runtime.d.ts.map +1 -0
- package/lib/scanner-owner-runtime.js +99 -0
- package/lib/scanner-owner-runtime.js.map +1 -0
- package/lib/service.d.ts +82 -5
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +450 -45
- package/lib/service.js.map +1 -1
- package/lib/source-approval-authority.d.ts.map +1 -1
- package/lib/source-approval-authority.js +25 -7
- package/lib/source-approval-authority.js.map +1 -1
- package/lib/source-build-diagnostics.d.ts +31 -0
- package/lib/source-build-diagnostics.d.ts.map +1 -0
- package/lib/source-build-diagnostics.js +110 -0
- package/lib/source-build-diagnostics.js.map +1 -0
- package/lib/source-build.d.ts +25 -2
- package/lib/source-build.d.ts.map +1 -1
- package/lib/source-build.js +145 -26
- package/lib/source-build.js.map +1 -1
- package/lib/source-creation.d.ts +6 -2
- package/lib/source-creation.d.ts.map +1 -1
- package/lib/source-creation.js +11 -2
- package/lib/source-creation.js.map +1 -1
- package/lib/source-job-types.d.ts +3 -1
- package/lib/source-job-types.d.ts.map +1 -1
- package/lib/source-jobs.d.ts +28 -1
- package/lib/source-jobs.d.ts.map +1 -1
- package/lib/source-jobs.js +117 -26
- package/lib/source-jobs.js.map +1 -1
- package/lib/sqlite.d.ts +1 -1
- package/lib/sqlite.d.ts.map +1 -1
- package/lib/sqlite.js +36 -1
- package/lib/sqlite.js.map +1 -1
- package/lib/store.d.ts +21 -1
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +470 -174
- package/lib/store.js.map +1 -1
- package/lib/systemd-host-authority.d.ts.map +1 -1
- package/lib/systemd-host-authority.js +4 -0
- package/lib/systemd-host-authority.js.map +1 -1
- package/lib/task-observation-runtime.d.ts +6 -2
- package/lib/task-observation-runtime.d.ts.map +1 -1
- package/lib/task-observation-runtime.js +69 -11
- package/lib/task-observation-runtime.js.map +1 -1
- package/lib/types.d.ts +47 -1
- package/lib/types.d.ts.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/work-item-creation-contract.d.ts +17 -0
- package/lib/work-item-creation-contract.d.ts.map +1 -0
- package/lib/work-item-creation-contract.js +61 -0
- package/lib/work-item-creation-contract.js.map +1 -0
- package/package.json +10 -10
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { controlPlaneDigest } from './store.js';
|
|
2
|
+
/** Below this many samples a measure is marked insufficient; latency percentiles are withheld, quality counts stay exact. */
|
|
3
|
+
export const DEFAULT_MINIMUM_SAMPLES = 5;
|
|
2
4
|
const same = (a, b) => controlPlaneDigest(a) === controlPlaneDigest(b);
|
|
3
5
|
/** Read the current canonical head and the corresponding authenticated Delivery source. */
|
|
4
6
|
function readTask(input, inboxId) {
|
|
5
|
-
const { owner, evaluation
|
|
7
|
+
const { owner, evaluation } = input;
|
|
6
8
|
const scope = evaluation.canonicalHostScope({ workspace: owner.workspace, preset: owner.agentPreset });
|
|
7
|
-
|
|
9
|
+
return certifiedTask(input, evaluation.getTrustedForegroundLearningProjection({ scope, inboxId }), inboxId);
|
|
10
|
+
}
|
|
11
|
+
/** The authenticated Delivery source of one current certified foreground head, or undefined. */
|
|
12
|
+
function certifiedTask(input, canonical, inboxId) {
|
|
13
|
+
const { owner, delivery } = input;
|
|
8
14
|
if (!canonical || canonical.projection.subjectKind !== 'foreground-turn'
|
|
9
15
|
|| canonical.projection.subjectRef !== inboxId || canonical.projection.disposition !== 'upsert')
|
|
10
16
|
return undefined;
|
|
@@ -23,14 +29,20 @@ function readTask(input, inboxId) {
|
|
|
23
29
|
return undefined;
|
|
24
30
|
return source;
|
|
25
31
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
function taskModel(source) {
|
|
33
|
+
const selection = source.modelSelection;
|
|
34
|
+
if (source.modelSelectionState === 'missing')
|
|
35
|
+
return { state: 'missing' };
|
|
36
|
+
if (source.modelSelectionState !== 'frozen' || !selection || typeof selection.provider !== 'string'
|
|
37
|
+
|| typeof selection.model !== 'string' || (selection.reasoningEffort !== undefined
|
|
38
|
+
&& typeof selection.reasoningEffort !== 'string'))
|
|
39
|
+
return { state: 'inconsistent' };
|
|
40
|
+
return { state: 'frozen', provider: selection.provider, model: selection.model,
|
|
41
|
+
...(selection.reasoningEffort === undefined ? {} : { reasoningEffort: selection.reasoningEffort }) };
|
|
42
|
+
}
|
|
43
|
+
/** Completed foreground calls of this exact adoption grouped by Inbox; undefined for a mixed-session Inbox. */
|
|
44
|
+
function eligibleCalls(input, receipt, receiptDigest, now) {
|
|
45
|
+
const { record, calls, owner } = input;
|
|
34
46
|
const grouped = new Map();
|
|
35
47
|
for (const call of calls) {
|
|
36
48
|
const proof = call.foreground;
|
|
@@ -51,14 +63,54 @@ export function inspectCreationCapabilityTaskAssociations(input) {
|
|
|
51
63
|
continue;
|
|
52
64
|
const prior = grouped.get(proof.task.inboxId);
|
|
53
65
|
if (prior && prior.sessionId !== proof.task.sessionId)
|
|
54
|
-
return
|
|
66
|
+
return undefined;
|
|
55
67
|
if (prior)
|
|
56
68
|
prior.calls.push(call);
|
|
57
69
|
else
|
|
58
70
|
grouped.set(proof.task.inboxId, { sessionId: proof.task.sessionId, calls: [call] });
|
|
59
71
|
}
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
return grouped;
|
|
73
|
+
}
|
|
74
|
+
/** Owner-fenced original task source whose binding matches the current learning head. */
|
|
75
|
+
function originalTaskSource(input, source, inboxId) {
|
|
76
|
+
const { owner, delivery } = input;
|
|
77
|
+
const originalSource = delivery.inspectOwnerForegroundTaskSource({ authorityId: owner.authorityId,
|
|
78
|
+
principalId: owner.principalId, workspace: owner.workspace, agentPreset: owner.agentPreset,
|
|
79
|
+
expectedOwner: { authorityHash: owner.authorityHash, principalRecordId: owner.principalRecordId,
|
|
80
|
+
principalVersion: owner.principalVersion }, inboxId });
|
|
81
|
+
if (!originalSource || source.source.sessionId !== originalSource.binding.sessionId
|
|
82
|
+
|| source.owner.bindingVersion !== originalSource.binding.version
|
|
83
|
+
|| source.owner.generation !== originalSource.binding.generation
|
|
84
|
+
|| originalSource.authorityId !== owner.authorityId || originalSource.authorityHash !== owner.authorityHash
|
|
85
|
+
|| originalSource.principalId !== owner.principalId
|
|
86
|
+
|| originalSource.owner.principalRecordId !== owner.principalRecordId
|
|
87
|
+
|| originalSource.owner.principalVersion !== owner.principalVersion)
|
|
88
|
+
return undefined;
|
|
89
|
+
return originalSource;
|
|
90
|
+
}
|
|
91
|
+
const sameBinding = (call, binding) => {
|
|
92
|
+
const task = call.foreground.task;
|
|
93
|
+
return task.binding.id === binding.id && task.binding.version === binding.version && task.binding.generation === binding.generation;
|
|
94
|
+
};
|
|
95
|
+
const taskLatencyMs = (execution) => execution?.quiescent && Number.isSafeInteger(execution.completedAt)
|
|
96
|
+
&& Number.isSafeInteger(execution.dispatchedAt) && execution.completedAt >= execution.dispatchedAt
|
|
97
|
+
? execution.completedAt - execution.dispatchedAt : undefined;
|
|
98
|
+
/**
|
|
99
|
+
* One fenced read of grouped candidate Inboxes into current association rows. Undefined means no receipt, a mixed
|
|
100
|
+
* session Inbox, a stale or changed fence, or a throw; `fenceEmpty` still checks the original task with no candidates.
|
|
101
|
+
*/
|
|
102
|
+
function readAssociationRows(input, fenceEmpty) {
|
|
103
|
+
const { record, owner, evaluation } = input;
|
|
104
|
+
const receipt = record.receipt;
|
|
105
|
+
if (!receipt || receipt.planId !== record.planId || input.triggerInboxId.length === 0)
|
|
106
|
+
return undefined;
|
|
107
|
+
const now = input.now ?? Date.now();
|
|
108
|
+
const receiptDigest = controlPlaneDigest(receipt);
|
|
109
|
+
const grouped = eligibleCalls(input, receipt, receiptDigest, now);
|
|
110
|
+
if (!grouped)
|
|
111
|
+
return undefined;
|
|
112
|
+
if (grouped.size === 0 && !fenceEmpty)
|
|
113
|
+
return { rows: [], candidates: 0, receiptDigest, now };
|
|
62
114
|
const scope = evaluation.canonicalHostScope({ workspace: owner.workspace, preset: owner.agentPreset });
|
|
63
115
|
const read = () => {
|
|
64
116
|
const original = input.readSourceCurrent();
|
|
@@ -67,49 +119,38 @@ export function inspectCreationCapabilityTaskAssociations(input) {
|
|
|
67
119
|
throw new Error('created capability original task is no longer current');
|
|
68
120
|
}
|
|
69
121
|
const evidence = [original.projection];
|
|
70
|
-
const
|
|
122
|
+
const rows = [];
|
|
71
123
|
let watermark = original.scopeWatermark;
|
|
72
124
|
for (const [inboxId, group] of grouped) {
|
|
73
125
|
const source = readTask(input, inboxId);
|
|
74
126
|
if (!source)
|
|
75
127
|
continue;
|
|
76
|
-
const originalSource =
|
|
77
|
-
|
|
78
|
-
expectedOwner: { authorityHash: owner.authorityHash, principalRecordId: owner.principalRecordId,
|
|
79
|
-
principalVersion: owner.principalVersion }, inboxId });
|
|
80
|
-
if (!originalSource || originalSource.binding.sessionId !== group.sessionId
|
|
81
|
-
|| source.source.sessionId !== group.sessionId
|
|
82
|
-
|| source.owner.bindingVersion !== originalSource.binding.version
|
|
83
|
-
|| source.owner.generation !== originalSource.binding.generation
|
|
84
|
-
|| originalSource.authorityId !== owner.authorityId || originalSource.authorityHash !== owner.authorityHash
|
|
85
|
-
|| originalSource.principalId !== owner.principalId
|
|
86
|
-
|| originalSource.owner.principalRecordId !== owner.principalRecordId
|
|
87
|
-
|| originalSource.owner.principalVersion !== owner.principalVersion)
|
|
128
|
+
const originalSource = originalTaskSource(input, source, inboxId);
|
|
129
|
+
if (!originalSource || originalSource.binding.sessionId !== group.sessionId)
|
|
88
130
|
continue;
|
|
89
|
-
const valid = group.calls.filter(call =>
|
|
90
|
-
const task = call.foreground.task;
|
|
91
|
-
return task.binding.id === originalSource.binding.id && task.binding.version === originalSource.binding.version
|
|
92
|
-
&& task.binding.generation === originalSource.binding.generation;
|
|
93
|
-
});
|
|
131
|
+
const valid = group.calls.filter(call => sameBinding(call, originalSource.binding));
|
|
94
132
|
if (valid.length === 0)
|
|
95
133
|
continue;
|
|
96
134
|
evidence.push(source.canonical.projection);
|
|
97
135
|
watermark = Math.max(watermark, source.canonical.scopeWatermark);
|
|
98
136
|
const projection = source.canonical.projection;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
137
|
+
const latencyMs = taskLatencyMs(originalSource.execution);
|
|
138
|
+
rows.push({ association: { protocol: 'dsh-created-capability-task-association/v1', planId: record.planId,
|
|
139
|
+
inboxId, sessionId: group.sessionId, callKeys: valid.map(call => call.key).sort(),
|
|
140
|
+
receiptDigest, artifactSha256: receipt.artifactSha256, schemaDigest: receipt.schemaDigest,
|
|
141
|
+
adoptionStatus: record.status,
|
|
142
|
+
withinSignedUseWindow: now >= receipt.adoptedAt && now < receipt.expiresAt,
|
|
143
|
+
task: { projection: { subjectKind: 'foreground-turn', subjectRef: inboxId, version: projection.version,
|
|
144
|
+
digest: projection.digest, disposition: 'upsert' }, scopeWatermark: source.canonical.scopeWatermark,
|
|
145
|
+
outcomeId: source.canonical.triggerOutcomeId,
|
|
146
|
+
judgement: source.judgement,
|
|
147
|
+
status: source.canonical.objective.status,
|
|
148
|
+
sourceDigest: controlPlaneDigest({ protocol: source.protocol, source: source.source,
|
|
149
|
+
judgement: source.judgement, ownerRevision: source.ownerRevision }) } },
|
|
150
|
+
model: taskModel(source.source), callLatencyMs: valid.map(call => call.settledAt - call.claimedAt),
|
|
151
|
+
...(latencyMs === undefined ? {} : { taskLatencyMs: latencyMs }) });
|
|
111
152
|
}
|
|
112
|
-
return { original,
|
|
153
|
+
return { original, rows, evidence, watermark };
|
|
113
154
|
};
|
|
114
155
|
try {
|
|
115
156
|
const before = read();
|
|
@@ -118,15 +159,456 @@ export function inspectCreationCapabilityTaskAssociations(input) {
|
|
|
118
159
|
const during = read();
|
|
119
160
|
if (!same(during, before))
|
|
120
161
|
throw new Error('created capability task association source changed');
|
|
121
|
-
return during.
|
|
162
|
+
return during.rows;
|
|
163
|
+
});
|
|
164
|
+
return result.matched ? { rows: result.value, candidates: grouped.size, receiptDigest, now } : undefined;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/** No cache: every result is checked against the current canonical writer and owner route. */
|
|
171
|
+
export function inspectCreationCapabilityTaskAssociations(input) {
|
|
172
|
+
const rows = readAssociationRows(input, false)?.rows ?? [];
|
|
173
|
+
return rows.length === 0 ? [] : Object.freeze(rows.map(({ association: item }) => Object.freeze({ ...item,
|
|
174
|
+
callKeys: Object.freeze([...item.callKeys]),
|
|
175
|
+
task: Object.freeze({ ...item.task, projection: Object.freeze({ ...item.task.projection }) }),
|
|
176
|
+
})));
|
|
177
|
+
}
|
|
178
|
+
const modelKey = (model) => model.state === 'frozen'
|
|
179
|
+
? JSON.stringify([model.state, model.provider, model.model, model.reasoningEffort ?? null]) : JSON.stringify([model.state]);
|
|
180
|
+
function latency(values, minimumSamples) {
|
|
181
|
+
if (values.length === 0)
|
|
182
|
+
return { status: 'unmeasured', samples: 0 };
|
|
183
|
+
if (values.length < minimumSamples)
|
|
184
|
+
return { status: 'insufficient', samples: values.length };
|
|
185
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
186
|
+
return { status: 'measured', samples: sorted.length, p50Ms: sorted[Math.ceil(sorted.length / 2) - 1],
|
|
187
|
+
maxMs: sorted[sorted.length - 1] };
|
|
188
|
+
}
|
|
189
|
+
function measures(rows, minimumSamples) {
|
|
190
|
+
const achieved = rows.filter(row => row.association.task.status === 'achieved').length;
|
|
191
|
+
return { quality: { status: rows.length < minimumSamples ? 'insufficient' : 'measured', samples: rows.length,
|
|
192
|
+
achieved, notAchieved: rows.length - achieved },
|
|
193
|
+
callLatency: latency(rows.flatMap(row => row.callLatencyMs), minimumSamples),
|
|
194
|
+
taskLatency: latency(rows.flatMap(row => row.taskLatencyMs === undefined ? [] : [row.taskLatencyMs]), minimumSamples) };
|
|
195
|
+
}
|
|
196
|
+
function deepFreeze(value) {
|
|
197
|
+
if (typeof value === 'object' && value !== null) {
|
|
198
|
+
for (const item of Object.values(value))
|
|
199
|
+
deepFreeze(item);
|
|
200
|
+
Object.freeze(value);
|
|
201
|
+
}
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Descriptive, non-causal counts over the same fenced current associations: one latest-revision quality sample per
|
|
206
|
+
* Inbox, strata by frozen task model only. Undefined only for a missing receipt, a failed fence or a throw.
|
|
207
|
+
*/
|
|
208
|
+
export function inspectCreationCapabilityOrdinaryUseReport(input) {
|
|
209
|
+
const minimumSamples = input.minimumSamples ?? DEFAULT_MINIMUM_SAMPLES;
|
|
210
|
+
if (!Number.isSafeInteger(minimumSamples) || minimumSamples < 1) {
|
|
211
|
+
throw new TypeError('created capability ordinary-use minimumSamples must be a positive safe integer');
|
|
212
|
+
}
|
|
213
|
+
const read = readAssociationRows(input, true);
|
|
214
|
+
const receipt = input.record.receipt;
|
|
215
|
+
if (!read || !receipt)
|
|
216
|
+
return undefined;
|
|
217
|
+
const rows = [...read.rows].sort((a, b) => a.association.inboxId < b.association.inboxId ? -1
|
|
218
|
+
: a.association.inboxId > b.association.inboxId ? 1 : 0);
|
|
219
|
+
const buckets = new Map();
|
|
220
|
+
for (const row of rows) {
|
|
221
|
+
const key = modelKey(row.model);
|
|
222
|
+
const bucket = buckets.get(key);
|
|
223
|
+
if (bucket)
|
|
224
|
+
bucket.rows.push(row);
|
|
225
|
+
else
|
|
226
|
+
buckets.set(key, { model: row.model, rows: [row] });
|
|
227
|
+
}
|
|
228
|
+
const strata = [...buckets].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)
|
|
229
|
+
.map(([, bucket]) => ({ model: bucket.model, ...measures(bucket.rows, minimumSamples) }));
|
|
230
|
+
return deepFreeze({ protocol: 'dsh-created-capability-ordinary-use-report/v1', causal: false,
|
|
231
|
+
planId: input.record.planId, receiptDigest: read.receiptDigest, artifactSha256: receipt.artifactSha256,
|
|
232
|
+
schemaDigest: receipt.schemaDigest, adoptionStatus: input.record.status,
|
|
233
|
+
withinSignedUseWindow: read.now >= receipt.adoptedAt && read.now < receipt.expiresAt, minimumSamples,
|
|
234
|
+
associationsDigest: controlPlaneDigest(rows.map(row => row.association)),
|
|
235
|
+
unknown: { inboxes: read.candidates - rows.length }, overall: measures(rows, minimumSamples), strata,
|
|
236
|
+
regressions: { status: 'unmeasured' }, cost: { status: 'unmeasured' } });
|
|
237
|
+
}
|
|
238
|
+
export const DEFAULT_MAX_SCANNED_HEADS = 5_000;
|
|
239
|
+
/** Every current canonical head of the scope under one unchanged scope watermark; throws when it moves or overflows. */
|
|
240
|
+
function scanScope(evaluation, scope, maxScannedHeads) {
|
|
241
|
+
const heads = [];
|
|
242
|
+
let after;
|
|
243
|
+
let scopeWatermark;
|
|
244
|
+
for (;;) {
|
|
245
|
+
const page = evaluation.listTrustedTaskLearningProjections({ scope, ...(after ? { after } : {}), limit: 100 });
|
|
246
|
+
if (scopeWatermark !== undefined && page.scopeWatermark !== scopeWatermark) {
|
|
247
|
+
throw new Error('created capability attribution scope changed during scan');
|
|
248
|
+
}
|
|
249
|
+
scopeWatermark = page.scopeWatermark;
|
|
250
|
+
heads.push(...page.items.map(item => item.receipt));
|
|
251
|
+
if (heads.length > maxScannedHeads)
|
|
252
|
+
throw new Error('created capability attribution scope is too large');
|
|
253
|
+
if (!page.hasMore)
|
|
254
|
+
return { heads, scopeWatermark: page.scopeWatermark };
|
|
255
|
+
after = page.nextCursor;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function arm(rows, minimumSamples) {
|
|
259
|
+
const achieved = rows.filter(row => row.status === 'achieved').length;
|
|
260
|
+
return { quality: { status: rows.length < minimumSamples ? 'insufficient' : 'measured', samples: rows.length,
|
|
261
|
+
achieved, notAchieved: rows.length - achieved },
|
|
262
|
+
taskLatency: latency(rows.flatMap(row => row.taskLatencyMs === undefined ? [] : [row.taskLatencyMs]), minimumSamples) };
|
|
263
|
+
}
|
|
264
|
+
/** a1/n1 - a0/n0 = (a1*n0 - a0*n1) / (n0*n1), exact and unreduced. */
|
|
265
|
+
function delta(before, after) {
|
|
266
|
+
if (before.quality.status !== 'measured' || after.quality.status !== 'measured')
|
|
267
|
+
return { status: 'insufficient' };
|
|
268
|
+
const numerator = after.quality.achieved * before.quality.samples - before.quality.achieved * after.quality.samples;
|
|
269
|
+
return { status: 'paired', numerator, denominator: before.quality.samples * after.quality.samples,
|
|
270
|
+
sign: numerator > 0 ? 1 : numerator < 0 ? -1 : 0 };
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Intention-to-treat before/after attribution of one adoption: every certified current owner foreground task (one
|
|
274
|
+
* latest-revision head per Inbox; corrections replace, withdrawals retract) in equal windows around adoption, by
|
|
275
|
+
* frozen task model. Read once, then reread under an unchanged Evaluation scope watermark. Undefined for a missing
|
|
276
|
+
* receipt, an oversized scope, a changed source, scope or fence, or a throw. Observational, never causal.
|
|
277
|
+
*/
|
|
278
|
+
export function inspectCreationCapabilityAvailabilityAttribution(input) {
|
|
279
|
+
const minimumSamples = input.minimumSamples ?? DEFAULT_MINIMUM_SAMPLES;
|
|
280
|
+
const maxScannedHeads = input.maxScannedHeads ?? DEFAULT_MAX_SCANNED_HEADS;
|
|
281
|
+
for (const [name, value] of [['minimumSamples', minimumSamples], ['maxScannedHeads', maxScannedHeads]]) {
|
|
282
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
283
|
+
throw new TypeError(`created capability attribution ${name} must be a positive safe integer`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (input.windowMs !== undefined && (!Number.isSafeInteger(input.windowMs) || input.windowMs < 1)) {
|
|
287
|
+
throw new TypeError('created capability attribution windowMs must be a positive safe integer');
|
|
288
|
+
}
|
|
289
|
+
const { record, owner, evaluation } = input;
|
|
290
|
+
const receipt = record.receipt;
|
|
291
|
+
if (!receipt || receipt.planId !== record.planId || input.triggerInboxId.length === 0)
|
|
292
|
+
return undefined;
|
|
293
|
+
const now = input.now ?? Date.now();
|
|
294
|
+
const receiptDigest = controlPlaneDigest(receipt);
|
|
295
|
+
const elapsed = Math.max(0, Math.min(now, receipt.expiresAt) - receipt.adoptedAt);
|
|
296
|
+
const windowMs = Math.min(input.windowMs ?? elapsed, elapsed);
|
|
297
|
+
const grouped = eligibleCalls(input, receipt, receiptDigest, now);
|
|
298
|
+
if (!grouped)
|
|
299
|
+
return undefined;
|
|
300
|
+
const scope = evaluation.canonicalHostScope({ workspace: owner.workspace, preset: owner.agentPreset });
|
|
301
|
+
const scan = () => scanScope(evaluation, scope, maxScannedHeads);
|
|
302
|
+
const read = (heads) => {
|
|
303
|
+
const original = input.readSourceCurrent();
|
|
304
|
+
if (original.projection.subjectKind !== 'foreground-turn'
|
|
305
|
+
|| original.projection.subjectRef !== input.triggerInboxId || original.projection.disposition !== 'upsert') {
|
|
306
|
+
throw new Error('created capability original task is no longer current');
|
|
307
|
+
}
|
|
308
|
+
const excluded = { nonForeground: 0, trigger: 0, retracted: 0, unverified: 0, outsideWindow: 0 };
|
|
309
|
+
const rows = [];
|
|
310
|
+
for (const head of heads) {
|
|
311
|
+
const inboxId = head.projection.subjectRef;
|
|
312
|
+
if (head.projection.subjectKind !== 'foreground-turn') {
|
|
313
|
+
excluded.nonForeground++;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (inboxId === input.triggerInboxId) {
|
|
317
|
+
excluded.trigger++;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (head.projection.disposition !== 'upsert') {
|
|
321
|
+
excluded.retracted++;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const source = certifiedTask(input, head, inboxId);
|
|
325
|
+
const originalSource = source && originalTaskSource(input, source, inboxId);
|
|
326
|
+
const dispatchedAt = originalSource?.execution?.dispatchedAt;
|
|
327
|
+
if (!source || !originalSource || !Number.isSafeInteger(dispatchedAt)) {
|
|
328
|
+
excluded.unverified++;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
const at = dispatchedAt;
|
|
332
|
+
const side = windowMs > 0 && at >= receipt.adoptedAt - windowMs && at <= receipt.adoptedAt ? 'before'
|
|
333
|
+
: windowMs > 0 && at > receipt.adoptedAt && at <= receipt.adoptedAt + windowMs ? 'after' : undefined;
|
|
334
|
+
if (!side) {
|
|
335
|
+
excluded.outsideWindow++;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const group = grouped.get(inboxId);
|
|
339
|
+
const latencyMs = taskLatencyMs(originalSource.execution);
|
|
340
|
+
rows.push({ arm: side, inboxId, model: taskModel(source.source),
|
|
341
|
+
status: source.canonical.objective.status,
|
|
342
|
+
projection: { version: head.projection.version, digest: head.projection.digest },
|
|
343
|
+
outcomeId: head.triggerOutcomeId,
|
|
344
|
+
sourceDigest: controlPlaneDigest({ protocol: source.protocol, source: source.source,
|
|
345
|
+
judgement: source.judgement, ownerRevision: source.ownerRevision }),
|
|
346
|
+
exposed: side === 'after' && group !== undefined && group.sessionId === originalSource.binding.sessionId
|
|
347
|
+
&& group.calls.some(call => sameBinding(call, originalSource.binding)),
|
|
348
|
+
...(latencyMs === undefined ? {} : { taskLatencyMs: latencyMs }) });
|
|
349
|
+
}
|
|
350
|
+
rows.sort((a, b) => a.inboxId < b.inboxId ? -1 : a.inboxId > b.inboxId ? 1 : 0);
|
|
351
|
+
return { original, rows, excluded };
|
|
352
|
+
};
|
|
353
|
+
let result;
|
|
354
|
+
try {
|
|
355
|
+
const scanned = scan();
|
|
356
|
+
const before = read(scanned.heads);
|
|
357
|
+
// An unchanged scope watermark means an unchanged feed; Delivery and the original source are reread inside.
|
|
358
|
+
const fenced = evaluation.withTrustedCanonicalScopeWriterFence({ scope, scopeWatermark: scanned.scopeWatermark }, () => {
|
|
359
|
+
const during = read(scanned.heads);
|
|
360
|
+
if (!same(during, before))
|
|
361
|
+
throw new Error('created capability attribution source changed');
|
|
362
|
+
return during;
|
|
363
|
+
});
|
|
364
|
+
if (!fenced.matched)
|
|
365
|
+
return undefined;
|
|
366
|
+
result = { ...scanned, read: fenced.value };
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
return undefined;
|
|
370
|
+
}
|
|
371
|
+
const { rows, excluded } = result.read;
|
|
372
|
+
const buckets = new Map();
|
|
373
|
+
for (const row of rows) {
|
|
374
|
+
const key = modelKey(row.model);
|
|
375
|
+
const bucket = buckets.get(key);
|
|
376
|
+
if (bucket)
|
|
377
|
+
bucket.rows.push(row);
|
|
378
|
+
else
|
|
379
|
+
buckets.set(key, { model: row.model, rows: [row] });
|
|
380
|
+
}
|
|
381
|
+
const strata = [...buckets].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)
|
|
382
|
+
.map(([, bucket]) => {
|
|
383
|
+
const beforeArm = arm(bucket.rows.filter(row => row.arm === 'before'), minimumSamples);
|
|
384
|
+
const afterArm = arm(bucket.rows.filter(row => row.arm === 'after'), minimumSamples);
|
|
385
|
+
return { model: bucket.model, before: beforeArm, after: afterArm, achievedRateDelta: delta(beforeArm, afterArm) };
|
|
386
|
+
});
|
|
387
|
+
const signs = new Set(strata.flatMap(stratum => stratum.achievedRateDelta.status === 'paired'
|
|
388
|
+
? [stratum.achievedRateDelta.sign] : []));
|
|
389
|
+
const direction = signs.size === 0 ? 'insufficient' : signs.size > 1 ? 'mixed'
|
|
390
|
+
: signs.has(1) ? 'improved' : signs.has(-1) ? 'regressed' : 'unchanged';
|
|
391
|
+
const concurrent = windowMs === 0 ? 0 : (input.otherAdoptedAt ?? []).filter(at => Number.isFinite(at)
|
|
392
|
+
&& at >= receipt.adoptedAt - windowMs && at <= receipt.adoptedAt + windowMs).length;
|
|
393
|
+
const afterRows = rows.filter(row => row.arm === 'after');
|
|
394
|
+
return deepFreeze({ protocol: 'dsh-created-capability-availability-attribution/v1', causal: false,
|
|
395
|
+
design: 'adoption-before-after-itt-model-stratified', planId: record.planId, receiptDigest,
|
|
396
|
+
artifactSha256: receipt.artifactSha256, schemaDigest: receipt.schemaDigest, adoptionStatus: record.status,
|
|
397
|
+
withinSignedUseWindow: now >= receipt.adoptedAt && now < receipt.expiresAt, minimumSamples, windowMs,
|
|
398
|
+
scopeWatermark: result.scopeWatermark, rowsDigest: controlPlaneDigest(rows),
|
|
399
|
+
population: { scanned: result.heads.length, counted: rows.length, excluded },
|
|
400
|
+
exposure: { afterTasks: afterRows.length, exposedTasks: afterRows.filter(row => row.exposed).length },
|
|
401
|
+
concurrentAdoptions: { status: concurrent === 0 ? 'none' : 'flagged', count: concurrent },
|
|
402
|
+
strata, direction, regressions: { status: 'unmeasured' }, cost: { status: 'unmeasured' } });
|
|
403
|
+
}
|
|
404
|
+
export const TREND_DEFAULT_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
|
|
405
|
+
export const TREND_WINDOW_MS_BOUNDS = { min: 60 * 60 * 1000, max: 90 * 24 * 60 * 60 * 1000 };
|
|
406
|
+
export const TREND_DEFAULT_PRE_WINDOWS = 4;
|
|
407
|
+
export const TREND_DEFAULT_MAX_POST_WINDOWS = 12;
|
|
408
|
+
export const TREND_WINDOW_COUNT_MAX = 52;
|
|
409
|
+
export const TREND_DEFAULT_PARALLEL_TREND_THRESHOLD = 0.2;
|
|
410
|
+
/** Fixed, machine-readable statement of what the estimate assumes; it is never a causal claim. */
|
|
411
|
+
export const TREND_ASSUMPTIONS = Object.freeze([
|
|
412
|
+
'parallel-trends: without this adoption the treated and control session cohorts would change achieved rate equally',
|
|
413
|
+
'session-cohort: a session is treated when a counted after-window task in it has an exact call of this adoption; membership is chosen after adoption, so selection on post-adoption use is not removed',
|
|
414
|
+
'no-spillover: control sessions are assumed unaffected although the capability is available to them',
|
|
415
|
+
'independent-tasks: the 95% interval is a four-cell Price-Bonett adjusted Wald interval (each cell (y+0.5)/(n+1)), centered on the adjusted estimate, over independent task outcomes',
|
|
416
|
+
'session-rotation: after an owner DM /new rotation the earlier session is a time-split control of the same owner, not a parallel one',
|
|
417
|
+
'availability: after windows end at min(now, expiresAt) only; a non-active adoption record (closed, rejected, unknown) is still treated as available and flagged adoption-not-active',
|
|
418
|
+
]);
|
|
419
|
+
const quality = (rows, minimumSamples) => {
|
|
420
|
+
const achieved = rows.filter(row => row.status === 'achieved').length;
|
|
421
|
+
return { status: rows.length < minimumSamples ? 'insufficient' : 'measured', samples: rows.length,
|
|
422
|
+
achieved, notAchieved: rows.length - achieved };
|
|
423
|
+
};
|
|
424
|
+
const rate = (cell) => cell.achieved / cell.samples;
|
|
425
|
+
function differenceInDifferences(cells) {
|
|
426
|
+
if (cells.some(cell => cell.status !== 'measured'))
|
|
427
|
+
return { status: 'insufficient' };
|
|
428
|
+
const [t0, t1, c0, c1] = cells;
|
|
429
|
+
const estimate = (rate(t1) - rate(t0)) - (rate(c1) - rate(c0));
|
|
430
|
+
// Price-Bonett: half a success and half a failure per cell, interval centered on the adjusted contrast.
|
|
431
|
+
const adjusted = (cell) => (cell.achieved + 0.5) / (cell.samples + 1);
|
|
432
|
+
const adjustedEstimate = (adjusted(t1) - adjusted(t0)) - (adjusted(c1) - adjusted(c0));
|
|
433
|
+
const standardError = Math.sqrt(cells.reduce((sum, cell) => {
|
|
434
|
+
const p = adjusted(cell);
|
|
435
|
+
return sum + p * (1 - p) / (cell.samples + 1);
|
|
436
|
+
}, 0));
|
|
437
|
+
return { status: 'estimated', estimate, adjustedEstimate, standardError, interval95: {
|
|
438
|
+
low: Math.max(-2, adjustedEstimate - 1.96 * standardError), high: Math.min(2, adjustedEstimate + 1.96 * standardError)
|
|
439
|
+
} };
|
|
440
|
+
}
|
|
441
|
+
function parallelTrend(cells, threshold) {
|
|
442
|
+
const tested = cells.filter(cell => cell.index < 0 && cell.treated.status === 'measured' && cell.control.status === 'measured');
|
|
443
|
+
if (tested.length < 2)
|
|
444
|
+
return { status: 'untestable', testedWindows: tested.length };
|
|
445
|
+
const gap = (cell) => rate(cell.treated) - rate(cell.control);
|
|
446
|
+
const gapChange = gap(tested[tested.length - 1]) - gap(tested[0]);
|
|
447
|
+
return { status: Math.abs(gapChange) > threshold ? 'parallel-trend-suspect' : 'not-flagged',
|
|
448
|
+
testedWindows: tested.length, gapChange };
|
|
449
|
+
}
|
|
450
|
+
function boundedInteger(name, value, min, max) {
|
|
451
|
+
if (!Number.isSafeInteger(value) || value < min || value > max) {
|
|
452
|
+
throw new TypeError(`created capability ordinary-use trend ${name} must be an integer in [${min}, ${max}]`);
|
|
453
|
+
}
|
|
454
|
+
return value;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Longitudinal ordinary-use series of one adoption in fixed windows around adoption, by frozen task model, with a
|
|
458
|
+
* same-scope session-cohort control and a pooled difference-in-differences estimate. Every certified current owner
|
|
459
|
+
* foreground task counts once by its latest revision (corrections replace, withdrawals retract). Read once, then
|
|
460
|
+
* reread under an unchanged Evaluation scope watermark. Undefined for a missing receipt, an oversized scope, a changed
|
|
461
|
+
* source, scope or fence, or a throw. Pure read; observational, never causal.
|
|
462
|
+
*/
|
|
463
|
+
export function inspectCreationCapabilityOrdinaryUseTrend(input) {
|
|
464
|
+
const minimumSamples = boundedInteger('minimumSamples', input.minimumSamples ?? DEFAULT_MINIMUM_SAMPLES, 1, Number.MAX_SAFE_INTEGER);
|
|
465
|
+
const maxScannedHeads = boundedInteger('maxScannedHeads', input.maxScannedHeads ?? DEFAULT_MAX_SCANNED_HEADS, 1, Number.MAX_SAFE_INTEGER);
|
|
466
|
+
const windowMs = boundedInteger('windowMs', input.windowMs ?? TREND_DEFAULT_WINDOW_MS, TREND_WINDOW_MS_BOUNDS.min, TREND_WINDOW_MS_BOUNDS.max);
|
|
467
|
+
const preWindows = boundedInteger('preWindows', input.preWindows ?? TREND_DEFAULT_PRE_WINDOWS, 2, TREND_WINDOW_COUNT_MAX);
|
|
468
|
+
const maxPostWindows = boundedInteger('maxPostWindows', input.maxPostWindows ?? TREND_DEFAULT_MAX_POST_WINDOWS, 1, TREND_WINDOW_COUNT_MAX);
|
|
469
|
+
const threshold = input.parallelTrendThreshold ?? TREND_DEFAULT_PARALLEL_TREND_THRESHOLD;
|
|
470
|
+
if (!Number.isFinite(threshold) || threshold <= 0 || threshold > 1) {
|
|
471
|
+
throw new TypeError('created capability ordinary-use trend parallelTrendThreshold must be in (0, 1]');
|
|
472
|
+
}
|
|
473
|
+
const { record, owner, evaluation } = input;
|
|
474
|
+
const receipt = record.receipt;
|
|
475
|
+
if (!receipt || receipt.planId !== record.planId || input.triggerInboxId.length === 0)
|
|
476
|
+
return undefined;
|
|
477
|
+
const now = input.now ?? Date.now();
|
|
478
|
+
const receiptDigest = controlPlaneDigest(receipt);
|
|
479
|
+
const observedUntil = Math.max(receipt.adoptedAt, Math.min(now, receipt.expiresAt));
|
|
480
|
+
const postWindows = Math.min(maxPostWindows, Math.ceil((observedUntil - receipt.adoptedAt) / windowMs));
|
|
481
|
+
const windows = [];
|
|
482
|
+
for (let index = -preWindows; index < postWindows; index++) {
|
|
483
|
+
const fullEnd = receipt.adoptedAt + (index + 1) * windowMs;
|
|
484
|
+
// After windows never extend past min(now, expiresAt); the latest one is clipped there and marked incomplete.
|
|
485
|
+
windows.push({ index, startExclusive: fullEnd - windowMs,
|
|
486
|
+
endInclusive: index < 0 ? fullEnd : Math.min(fullEnd, observedUntil), complete: index < 0 || fullEnd <= observedUntil });
|
|
487
|
+
}
|
|
488
|
+
const first = windows[0].startExclusive;
|
|
489
|
+
const last = Math.min(windows[windows.length - 1].endInclusive, observedUntil);
|
|
490
|
+
// A dispatch exactly at adoption precedes it, matching the strict call admission rule. Dispatches after
|
|
491
|
+
// min(now, expiresAt) are outside every window, so an expired adoption stops accruing after tasks.
|
|
492
|
+
const windowOf = (at) => at <= first || at > last ? undefined
|
|
493
|
+
: Math.ceil((at - receipt.adoptedAt) / windowMs) - 1;
|
|
494
|
+
const grouped = eligibleCalls(input, receipt, receiptDigest, now);
|
|
495
|
+
if (!grouped)
|
|
496
|
+
return undefined;
|
|
497
|
+
const scope = evaluation.canonicalHostScope({ workspace: owner.workspace, preset: owner.agentPreset });
|
|
498
|
+
const read = (heads) => {
|
|
499
|
+
const original = input.readSourceCurrent();
|
|
500
|
+
if (original.projection.subjectKind !== 'foreground-turn'
|
|
501
|
+
|| original.projection.subjectRef !== input.triggerInboxId || original.projection.disposition !== 'upsert') {
|
|
502
|
+
throw new Error('created capability original task is no longer current');
|
|
503
|
+
}
|
|
504
|
+
const excluded = { nonForeground: 0, trigger: 0, retracted: 0, unverified: 0, outsideWindow: 0 };
|
|
505
|
+
const rows = [];
|
|
506
|
+
for (const head of heads) {
|
|
507
|
+
const inboxId = head.projection.subjectRef;
|
|
508
|
+
if (head.projection.subjectKind !== 'foreground-turn') {
|
|
509
|
+
excluded.nonForeground++;
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
if (inboxId === input.triggerInboxId) {
|
|
513
|
+
excluded.trigger++;
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
if (head.projection.disposition !== 'upsert') {
|
|
517
|
+
excluded.retracted++;
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
const source = certifiedTask(input, head, inboxId);
|
|
521
|
+
const originalSource = source && originalTaskSource(input, source, inboxId);
|
|
522
|
+
const dispatchedAt = originalSource?.execution?.dispatchedAt;
|
|
523
|
+
if (!source || !originalSource || !Number.isSafeInteger(dispatchedAt)) {
|
|
524
|
+
excluded.unverified++;
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
const index = windowOf(dispatchedAt);
|
|
528
|
+
if (index === undefined) {
|
|
529
|
+
excluded.outsideWindow++;
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
const group = grouped.get(inboxId);
|
|
533
|
+
const calls = index >= 0 && group && group.sessionId === originalSource.binding.sessionId
|
|
534
|
+
? group.calls.filter(call => sameBinding(call, originalSource.binding)).length : 0;
|
|
535
|
+
rows.push({ index, inboxId, sessionId: originalSource.binding.sessionId, model: taskModel(source.source),
|
|
536
|
+
status: source.canonical.objective.status,
|
|
537
|
+
projection: { version: head.projection.version, digest: head.projection.digest },
|
|
538
|
+
outcomeId: head.triggerOutcomeId,
|
|
539
|
+
sourceDigest: controlPlaneDigest({ protocol: source.protocol, source: source.source,
|
|
540
|
+
judgement: source.judgement, ownerRevision: source.ownerRevision }), calls });
|
|
541
|
+
}
|
|
542
|
+
rows.sort((a, b) => a.inboxId < b.inboxId ? -1 : a.inboxId > b.inboxId ? 1 : 0);
|
|
543
|
+
return { original, rows, excluded };
|
|
544
|
+
};
|
|
545
|
+
let result;
|
|
546
|
+
try {
|
|
547
|
+
const scanned = scanScope(evaluation, scope, maxScannedHeads);
|
|
548
|
+
const before = read(scanned.heads);
|
|
549
|
+
const fenced = evaluation.withTrustedCanonicalScopeWriterFence({ scope, scopeWatermark: scanned.scopeWatermark }, () => {
|
|
550
|
+
const during = read(scanned.heads);
|
|
551
|
+
if (!same(during, before))
|
|
552
|
+
throw new Error('created capability ordinary-use trend source changed');
|
|
553
|
+
return during;
|
|
122
554
|
});
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}))) : [];
|
|
555
|
+
if (!fenced.matched)
|
|
556
|
+
return undefined;
|
|
557
|
+
result = { ...scanned, read: fenced.value };
|
|
127
558
|
}
|
|
128
559
|
catch {
|
|
129
|
-
return
|
|
560
|
+
return undefined;
|
|
130
561
|
}
|
|
562
|
+
const { rows, excluded } = result.read;
|
|
563
|
+
const treatedSessions = new Set(rows.filter(row => row.calls > 0).map(row => row.sessionId));
|
|
564
|
+
const controlSessions = new Set(rows.filter(row => !treatedSessions.has(row.sessionId)).map(row => row.sessionId));
|
|
565
|
+
const buckets = new Map();
|
|
566
|
+
for (const row of rows) {
|
|
567
|
+
const key = modelKey(row.model);
|
|
568
|
+
const bucket = buckets.get(key);
|
|
569
|
+
if (bucket)
|
|
570
|
+
bucket.rows.push(row);
|
|
571
|
+
else
|
|
572
|
+
buckets.set(key, { model: row.model, rows: [row] });
|
|
573
|
+
}
|
|
574
|
+
const strata = [...buckets].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)
|
|
575
|
+
.map(([, bucket]) => {
|
|
576
|
+
const treated = bucket.rows.filter(row => treatedSessions.has(row.sessionId));
|
|
577
|
+
const control = bucket.rows.filter(row => !treatedSessions.has(row.sessionId));
|
|
578
|
+
const cells = windows.map(({ index }) => {
|
|
579
|
+
const inWindow = bucket.rows.filter(row => row.index === index);
|
|
580
|
+
return { index, treated: quality(treated.filter(row => row.index === index), minimumSamples),
|
|
581
|
+
control: quality(control.filter(row => row.index === index), minimumSamples),
|
|
582
|
+
calls: inWindow.reduce((sum, row) => sum + row.calls, 0),
|
|
583
|
+
exposedTasks: inWindow.filter(row => row.calls > 0).length };
|
|
584
|
+
});
|
|
585
|
+
const pooled = (rows, after) => quality(rows.filter(row => (row.index >= 0) === after), minimumSamples);
|
|
586
|
+
return { model: bucket.model, cells,
|
|
587
|
+
differenceInDifferences: differenceInDifferences([pooled(treated, false), pooled(treated, true),
|
|
588
|
+
pooled(control, false), pooled(control, true)]),
|
|
589
|
+
parallelTrend: parallelTrend(cells, threshold) };
|
|
590
|
+
});
|
|
591
|
+
const concurrent = (input.otherAdoptedAt ?? []).filter(at => Number.isFinite(at) && at > first && at <= last).length;
|
|
592
|
+
const flags = [];
|
|
593
|
+
if (strata.some(stratum => stratum.parallelTrend.status === 'parallel-trend-suspect'))
|
|
594
|
+
flags.push('parallel-trend-suspect');
|
|
595
|
+
if (controlSessions.size === 0)
|
|
596
|
+
flags.push('no-control-cohort');
|
|
597
|
+
if (concurrent > 0)
|
|
598
|
+
flags.push('concurrent-adoptions');
|
|
599
|
+
if (windows.some(window => !window.complete))
|
|
600
|
+
flags.push('partial-window');
|
|
601
|
+
if (record.status !== 'active')
|
|
602
|
+
flags.push('adoption-not-active');
|
|
603
|
+
return deepFreeze({ protocol: 'dsh-created-capability-ordinary-use-trend/v1', causal: false,
|
|
604
|
+
design: 'session-cohort-difference-in-differences-model-stratified', planId: record.planId, receiptDigest,
|
|
605
|
+
artifactSha256: receipt.artifactSha256, schemaDigest: receipt.schemaDigest, adoptionStatus: record.status,
|
|
606
|
+
withinSignedUseWindow: now >= receipt.adoptedAt && now < receipt.expiresAt, minimumSamples, windowMs,
|
|
607
|
+
parallelTrendThreshold: threshold, scopeWatermark: result.scopeWatermark,
|
|
608
|
+
rowsDigest: controlPlaneDigest(rows.map(({ sessionId: _sessionId, ...row }) => row)), windows,
|
|
609
|
+
population: { scanned: result.heads.length, counted: rows.length, excluded },
|
|
610
|
+
cohorts: { treatedSessions: treatedSessions.size, controlSessions: controlSessions.size },
|
|
611
|
+
concurrentAdoptions: { status: concurrent === 0 ? 'none' : 'flagged', count: concurrent },
|
|
612
|
+
strata, flags, assumptions: [...TREND_ASSUMPTIONS], regressions: { status: 'unmeasured' }, cost: { status: 'unmeasured' } });
|
|
131
613
|
}
|
|
132
614
|
//# sourceMappingURL=creation-capability-feedback.js.map
|