Files
PaperClipAI/packages/paperclip-runner/scripts/cursor-native-usage.mjs
DottaandPaperclip 986f8cc534 Verify Cursor child usage lifecycle and fence profile v9
Mark missing and invalid child run ordinals as partial observations. Exercise the pinned vendor child construction and reuse methods on all three platforms and regenerate the Cursor-only execution identity.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-09-30 05:33:44 -05:00

82 lines
4.3 KiB
JavaScript

/** Self-contained: embedded verbatim into the digest-pinned ACP module.
* Observations are NOT usage accounting. Native counter semantics remain unknown.
*/
export function createCursorNativeUsage(promptId) {
if (typeof promptId !== "string" || !/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(promptId)) throw new Error("Invalid Cursor usage prompt identity");
const limits = { maxObservations: 64, maxInvocations: 64, maxBytes: 16384 };
// Reserve bounded ACPX request/message identities and wrapper keys within 16 KiB.
const envelopeMaxBytes = limits.maxBytes - 640;
const fields = ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens", "reasoningTokens"];
const reasons = new Set(["native_counter_semantics_unverified"]);
const observations = [];
const children = new WeakMap();
const trackedInvocations = new Set(); // At most maxInvocations, including children.
let closed = false, truncated = false, invocations = 0;
let finished;
const envelope = () => ({
schema: "paperclip.cursor.native-usage.v1", source: "native_turn_ended", promptId,
completeness: "partial", reasons: [...reasons], observations, limits, truncated,
});
const truncate = () => { truncated = true; reasons.add("observation_limit_reached"); };
function begin(role, nativeRun) {
if (closed) return null;
if (invocations >= limits.maxInvocations) { truncate(); return null; }
const invocationId = `invocation-${++invocations}`;
let ended = false, sequence = 0;
const collector = {
observe(update) {
if (closed || ended || truncated || update?.message?.case !== "turnEnded") return;
if (observations.length >= limits.maxObservations) { truncate(); return; }
const value = update.message.value;
const counters = {};
for (const field of fields) {
const raw = value?.[field];
if (raw === undefined) continue;
const number = typeof raw === "bigint" && raw >= 0n && raw <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(raw) : raw;
if (typeof number === "number" && Number.isSafeInteger(number) && number >= 0) counters[field] = number;
else reasons.add("invalid_native_counter");
}
if (Object.keys(counters).length < fields.length) reasons.add("native_counters_missing");
if (sequence > 0) reasons.add("multiple_terminal_observations");
observations.push({ invocationId, role, nativeRun, sequence: ++sequence, counters });
},
end() { if (!ended && sequence === 0 && !closed) reasons.add("native_terminal_not_observed"); ended = true; },
};
trackedInvocations.add(collector);
return collector;
}
return {
beginParent() { return begin("parent", invocations + 1); },
observeChild(child, update) {
if (closed || !child || child.terminal) return;
if (!Number.isSafeInteger(child.runs) || child.runs < 1) { reasons.add("child_run_attribution_unverified"); return; }
// The native callback identifies only the child, not its run generation.
// After ID reuse, delayed old-run updates cannot be distinguished safely.
if (child.runs !== 1) { reasons.add("child_run_attribution_unverified"); return; }
let run = children.get(child);
if (!run || run.nativeRun !== child.runs) {
run?.collector?.end();
run = { nativeRun: child.runs, collector: begin("child", child.runs) };
children.set(child, run);
}
run.collector?.observe(update);
},
finish() {
if (finished !== undefined) return JSON.parse(finished);
for (const invocation of trackedInvocations) invocation.end();
trackedInvocations.clear();
closed = true;
if (observations.length === 0) reasons.add("native_terminal_not_observed");
// Bound the FINAL envelope after every reason has been added. Its fixed
// ASCII keys/IDs/reasons make JSON length equal to its UTF-8 byte length.
finished = JSON.stringify(envelope());
while (finished.length > envelopeMaxBytes && observations.length > 0) {
observations.pop(); truncate(); finished = JSON.stringify(envelope());
}
// Detached snapshot: late provider callbacks cannot mutate a returned receipt.
return JSON.parse(finished);
},
close() { closed = true; trackedInvocations.clear(); },
};
}