@erdoai/cli 0.49.0 → 0.50.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.js +246 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -916,6 +916,47 @@ var ErdoClient = class {
|
|
|
916
916
|
input
|
|
917
917
|
);
|
|
918
918
|
}
|
|
919
|
+
// --- decisions ---
|
|
920
|
+
// The decision record: what the organization committed to, what executed, and
|
|
921
|
+
// what the evidence said afterwards. Referenced by slug, never UUID.
|
|
922
|
+
listDecisions(opts) {
|
|
923
|
+
const params = new URLSearchParams();
|
|
924
|
+
if (opts.workstream_slug) params.set("workstream_slug", opts.workstream_slug);
|
|
925
|
+
if (opts.source) params.set("source", opts.source);
|
|
926
|
+
if (opts.decision_class) params.set("decision_class", opts.decision_class);
|
|
927
|
+
if (opts.subject_kind) params.set("subject_kind", opts.subject_kind);
|
|
928
|
+
if (opts.subject_ref) params.set("subject_ref", opts.subject_ref);
|
|
929
|
+
if (opts.status) params.set("status", opts.status);
|
|
930
|
+
if (opts.applicability) params.set("applicability", opts.applicability);
|
|
931
|
+
if (opts.outcome) params.set("outcome", opts.outcome);
|
|
932
|
+
if (opts.limit) params.set("limit", String(opts.limit));
|
|
933
|
+
if (opts.offset) params.set("offset", String(opts.offset));
|
|
934
|
+
const qs = params.toString();
|
|
935
|
+
return this.request("GET", `/v1/decisions${qs ? `?${qs}` : ""}`);
|
|
936
|
+
}
|
|
937
|
+
// One decision in full. A slug belonging to another organization reads as 404,
|
|
938
|
+
// so it cannot be probed for existence.
|
|
939
|
+
getDecision(slug) {
|
|
940
|
+
return this.request(
|
|
941
|
+
"GET",
|
|
942
|
+
`/v1/decisions/${encodeURIComponent(slug)}`
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
// Raw aggregates with their denominators — no eligibility verdict, and no single
|
|
946
|
+
// pooled "worked rate" across evidence kinds and decision classes.
|
|
947
|
+
decisionScorecard(opts) {
|
|
948
|
+
const params = new URLSearchParams();
|
|
949
|
+
if (opts.workstream_slug) params.set("workstream_slug", opts.workstream_slug);
|
|
950
|
+
if (opts.source) params.set("source", opts.source);
|
|
951
|
+
if (opts.decision_class) params.set("decision_class", opts.decision_class);
|
|
952
|
+
if (opts.since) params.set("since", opts.since);
|
|
953
|
+
if (opts.until) params.set("until", opts.until);
|
|
954
|
+
const qs = params.toString();
|
|
955
|
+
return this.request(
|
|
956
|
+
"GET",
|
|
957
|
+
`/v1/decisions-scorecard${qs ? `?${qs}` : ""}`
|
|
958
|
+
);
|
|
959
|
+
}
|
|
919
960
|
// --- pages / artifacts ---
|
|
920
961
|
deployPage(input) {
|
|
921
962
|
return this.request("POST", "/v1/pages", input);
|
|
@@ -1722,7 +1763,7 @@ wsCmd.command("arm <slug>").description("Arm the recurring reconciliation loop (
|
|
|
1722
1763
|
}
|
|
1723
1764
|
);
|
|
1724
1765
|
wsCmd.command("ledger <slug>").description(
|
|
1725
|
-
"Read the allocator's single ledger view \u2014 budget, experiments + observations, judge calibration, attention items, allocator recommendation"
|
|
1766
|
+
"Read the allocator's single ledger view \u2014 budget, experiments + observations, judge calibration, the decisions currently in force, attention items, allocator recommendation"
|
|
1726
1767
|
).option("--observations <n>", "observations to include per experiment", (v) => parseInt(v, 10)).action(async (slug, opts) => {
|
|
1727
1768
|
try {
|
|
1728
1769
|
print(await new ErdoClient().readWorkstreamLedger(slug, opts.observations));
|
|
@@ -2437,6 +2478,210 @@ runsCmd.command("get <id>").description("Show an agent run (status, output, trac
|
|
|
2437
2478
|
fail(e);
|
|
2438
2479
|
}
|
|
2439
2480
|
});
|
|
2481
|
+
var decisionsCmd = program.command("decisions").description(
|
|
2482
|
+
"The decision record \u2014 what your organization committed to, whether the change actually happened, and what the evidence said afterwards"
|
|
2483
|
+
);
|
|
2484
|
+
function decisionLine(d) {
|
|
2485
|
+
const parts = [d.slug, d.status];
|
|
2486
|
+
if (d.execution_status) parts.push(d.execution_status);
|
|
2487
|
+
parts.push(d.what);
|
|
2488
|
+
if (d.outcome_label) {
|
|
2489
|
+
parts.push(d.outcome_label);
|
|
2490
|
+
} else if (d.outcome_status) {
|
|
2491
|
+
parts.push(d.outcome_status);
|
|
2492
|
+
}
|
|
2493
|
+
return parts.join(" ");
|
|
2494
|
+
}
|
|
2495
|
+
decisionsCmd.command("list").description("List decisions, newest first").option("--workstream <slug>", "only decisions filed under this workstream/Strategy").option(
|
|
2496
|
+
"--source <source>",
|
|
2497
|
+
"producer family: approval | escalation | engine_gate | allocator | experiment | workstream_commitment"
|
|
2498
|
+
).option("--class <class>", "decision class, e.g. paid_media.ad_group.pause").option("--subject-kind <kind>", "only decisions about this kind of subject").option("--subject-ref <id>", "only decisions about this exact subject").option(
|
|
2499
|
+
"--status <status>",
|
|
2500
|
+
"proposed | authorized | executing | effective | measuring | settled | rejected | failed | censored"
|
|
2501
|
+
).option("--applicability <kind>", "standing | one_shot").option("--outcome <outcome>", "only decisions with a settled effect that came back met | not_met | inconclusive").option("-l, --limit <n>", "max rows (default 50, max 200)", (v) => parseInt(v, 10)).option("--offset <n>", "pagination offset", (v) => parseInt(v, 10)).option("--json", "output raw JSON").action(
|
|
2502
|
+
async (opts) => {
|
|
2503
|
+
try {
|
|
2504
|
+
const res = await new ErdoClient().listDecisions({
|
|
2505
|
+
workstream_slug: opts.workstream,
|
|
2506
|
+
source: opts.source,
|
|
2507
|
+
decision_class: opts.class,
|
|
2508
|
+
subject_kind: opts.subjectKind,
|
|
2509
|
+
subject_ref: opts.subjectRef,
|
|
2510
|
+
status: opts.status,
|
|
2511
|
+
applicability: opts.applicability,
|
|
2512
|
+
outcome: opts.outcome,
|
|
2513
|
+
limit: opts.limit,
|
|
2514
|
+
offset: opts.offset
|
|
2515
|
+
});
|
|
2516
|
+
if (opts.json) {
|
|
2517
|
+
print(res);
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2520
|
+
if (!res.decisions.length) {
|
|
2521
|
+
console.log("No decisions match. Widen the filters, or check that the work engine is on for this org.");
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
for (const d of res.decisions) console.log(decisionLine(d));
|
|
2525
|
+
} catch (e) {
|
|
2526
|
+
fail(e);
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
);
|
|
2530
|
+
function renderDecision(detail) {
|
|
2531
|
+
const d = detail.decision;
|
|
2532
|
+
console.log(d.what);
|
|
2533
|
+
const facts = [
|
|
2534
|
+
["status", d.status],
|
|
2535
|
+
["class", d.decision_class],
|
|
2536
|
+
["source", d.source],
|
|
2537
|
+
["applies", d.applicability === "standing" ? "standing (until superseded)" : "one-off authorization"],
|
|
2538
|
+
["decided by", d.decider_kind]
|
|
2539
|
+
];
|
|
2540
|
+
if (d.subject_label) facts.push(["subject", d.subject_label]);
|
|
2541
|
+
if (d.execution_status) facts.push(["execution", d.execution_status]);
|
|
2542
|
+
if (d.outcome_status) facts.push(["outcome", d.outcome_label || d.outcome_status]);
|
|
2543
|
+
facts.push(["proposed", d.proposed_at]);
|
|
2544
|
+
if (d.authorized_at) facts.push(["authorized", d.authorized_at]);
|
|
2545
|
+
for (const [label, value] of facts) console.log(` ${label.padEnd(11)} ${value}`);
|
|
2546
|
+
if (d.why) {
|
|
2547
|
+
console.log("");
|
|
2548
|
+
console.log(`Why: ${d.why}`);
|
|
2549
|
+
}
|
|
2550
|
+
if (d.decider_rationale) console.log(`Decider said: ${d.decider_rationale}`);
|
|
2551
|
+
if (detail.actions.length) {
|
|
2552
|
+
console.log("");
|
|
2553
|
+
console.log(detail.actions.length === 1 ? "Action:" : `Actions (${detail.actions.length}):`);
|
|
2554
|
+
detail.actions.forEach((a, i) => {
|
|
2555
|
+
console.log(` ${i + 1}. ${a.action_key} \u2014 ${a.execution_status}`);
|
|
2556
|
+
if (a.subject_label) console.log(` subject: ${a.subject_label}`);
|
|
2557
|
+
if (a.effective_at) console.log(` effective: ${a.effective_at}`);
|
|
2558
|
+
if (a.result_summary) console.log(` result: ${a.result_summary}`);
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
if (detail.effects.length) {
|
|
2562
|
+
console.log("");
|
|
2563
|
+
console.log(detail.effects.length === 1 ? "Expected effect:" : `Expected effects (${detail.effects.length}):`);
|
|
2564
|
+
detail.effects.forEach((e, i) => {
|
|
2565
|
+
const predicate = [e.metric, e.success_operator, e.target_value ?? e.min_delta].filter((p) => p !== void 0 && p !== null && p !== "").join(" ");
|
|
2566
|
+
console.log(` ${i + 1}. ${predicate || e.measurability} \u2014 ${e.status}`);
|
|
2567
|
+
if (e.outcome) {
|
|
2568
|
+
console.log(` outcome: ${e.outcome_label || e.outcome} (${e.evidence_kind} evidence)`);
|
|
2569
|
+
}
|
|
2570
|
+
if (e.baseline_value !== void 0 && e.baseline_value !== null && e.outcome_value !== void 0 && e.outcome_value !== null) {
|
|
2571
|
+
console.log(` measured: ${e.baseline_value} \u2192 ${e.outcome_value}`);
|
|
2572
|
+
}
|
|
2573
|
+
if (e.unmeasurable_reason) console.log(` reason: ${e.unmeasurable_reason}`);
|
|
2574
|
+
});
|
|
2575
|
+
}
|
|
2576
|
+
if (detail.lineage.supersedes_slug || detail.lineage.superseded_by_slugs?.length) {
|
|
2577
|
+
console.log("");
|
|
2578
|
+
console.log("Lineage:");
|
|
2579
|
+
if (detail.lineage.supersedes_slug) console.log(` replaced ${detail.lineage.supersedes_slug}`);
|
|
2580
|
+
for (const slug of detail.lineage.superseded_by_slugs ?? []) {
|
|
2581
|
+
console.log(` replaced by ${slug}`);
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
decisionsCmd.command("show <slug>").description(
|
|
2586
|
+
"Show one decision in full: the commitment and its authority, every exact action it authorized with how each one ended, every declared effect with the evidence that settled it, and what it replaced or was replaced by"
|
|
2587
|
+
).option("--json", "output raw JSON").action(async (slug, opts) => {
|
|
2588
|
+
try {
|
|
2589
|
+
const detail = await new ErdoClient().getDecision(slug);
|
|
2590
|
+
if (opts.json) {
|
|
2591
|
+
print(detail);
|
|
2592
|
+
return;
|
|
2593
|
+
}
|
|
2594
|
+
renderDecision(detail);
|
|
2595
|
+
} catch (e) {
|
|
2596
|
+
fail(e);
|
|
2597
|
+
}
|
|
2598
|
+
});
|
|
2599
|
+
function renderStrata(title, strata) {
|
|
2600
|
+
if (!strata.length) return;
|
|
2601
|
+
console.log("");
|
|
2602
|
+
console.log(title);
|
|
2603
|
+
for (const s of strata) {
|
|
2604
|
+
const axis = s.decision_class || s.source || "(unclassified)";
|
|
2605
|
+
console.log(` ${axis} \xB7 ${s.evidence_kind} evidence \u2014 ${s.settled} settled`);
|
|
2606
|
+
console.log(` ${s.met} ${s.met_label} \xB7 ${s.not_met} ${s.not_met_label} \xB7 ${s.inconclusive} inconclusive`);
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
function renderScorecard(card) {
|
|
2610
|
+
console.log("Decision record");
|
|
2611
|
+
console.log(` decisions ${card.totals.decisions}`);
|
|
2612
|
+
for (const [status, n] of Object.entries(card.totals.by_status)) {
|
|
2613
|
+
console.log(` ${status.padEnd(11)} ${n}`);
|
|
2614
|
+
}
|
|
2615
|
+
if (card.totals.legacy_unclassified > 0) {
|
|
2616
|
+
console.log(` legacy ${card.totals.legacy_unclassified} (predates expected-effect capture; excluded from outcomes below)`);
|
|
2617
|
+
}
|
|
2618
|
+
console.log("");
|
|
2619
|
+
console.log("Execution");
|
|
2620
|
+
console.log(` decisions with an external action ${card.execution.decisions_with_actions} of ${card.totals.decisions}`);
|
|
2621
|
+
console.log(` actions ${card.execution.actions}`);
|
|
2622
|
+
for (const [status, n] of Object.entries(card.execution.by_status)) {
|
|
2623
|
+
console.log(` ${status.padEnd(11)} ${n}`);
|
|
2624
|
+
}
|
|
2625
|
+
console.log("");
|
|
2626
|
+
console.log("Measurement coverage");
|
|
2627
|
+
console.log(
|
|
2628
|
+
` decisions that declared an effect ${card.measurement.decisions_with_declared_effect} of ${card.totals.decisions}`
|
|
2629
|
+
);
|
|
2630
|
+
console.log(` effects ${card.measurement.effects}`);
|
|
2631
|
+
console.log(` settled ${card.measurement.settled}`);
|
|
2632
|
+
console.log(` censored ${card.measurement.censored}`);
|
|
2633
|
+
console.log(` measurement unavailable ${card.measurement.measurement_unavailable}`);
|
|
2634
|
+
for (const [kind, n] of Object.entries(card.measurement.by_measurability)) {
|
|
2635
|
+
console.log(` ${kind.padEnd(29)} ${n}`);
|
|
2636
|
+
}
|
|
2637
|
+
renderStrata("Outcomes by decision class and evidence", card.outcomes_by_class_and_evidence);
|
|
2638
|
+
renderStrata("Outcomes by source and evidence", card.outcomes_by_source_and_evidence);
|
|
2639
|
+
console.log("");
|
|
2640
|
+
console.log("Deciders");
|
|
2641
|
+
console.log(` answered ${card.deciders.answered}`);
|
|
2642
|
+
for (const [kind, n] of Object.entries(card.deciders.by_decider_kind)) {
|
|
2643
|
+
console.log(` ${kind.padEnd(21)} ${n}`);
|
|
2644
|
+
}
|
|
2645
|
+
console.log(
|
|
2646
|
+
` safe default applied ${card.deciders.safe_default} of ${card.deciders.answered} (${(card.deciders.safe_default_rate * 100).toFixed(1)}%)`
|
|
2647
|
+
);
|
|
2648
|
+
console.log(
|
|
2649
|
+
` human refused a proposal ${card.deciders.human_override} of ${card.deciders.answered} (${(card.deciders.human_override_rate * 100).toFixed(1)}%)`
|
|
2650
|
+
);
|
|
2651
|
+
if (card.decide_latency.decided > 0) {
|
|
2652
|
+
console.log("");
|
|
2653
|
+
console.log("Time to authorize");
|
|
2654
|
+
console.log(` decisions authorized ${card.decide_latency.decided}`);
|
|
2655
|
+
console.log(` median ${(card.decide_latency.p50_seconds / 3600).toFixed(1)}h`);
|
|
2656
|
+
console.log(` 90th percentile ${(card.decide_latency.p90_seconds / 3600).toFixed(1)}h`);
|
|
2657
|
+
}
|
|
2658
|
+
console.log("");
|
|
2659
|
+
console.log(
|
|
2660
|
+
"Raw counts only \u2014 no eligibility verdict, and no single pooled rate: deterministic confirmation, experimental results and observational movement are counted separately because they support different claims."
|
|
2661
|
+
);
|
|
2662
|
+
}
|
|
2663
|
+
decisionsCmd.command("scorecard").description(
|
|
2664
|
+
"Raw aggregates over the decision record with their denominators: totals by status, how much executed, how much can be measured at all, and the settled outcome split within each comparable stratum"
|
|
2665
|
+
).option("--workstream <slug>", "score only this workstream/Strategy's decisions").option("--source <source>", "score only one producer family").option("--class <class>", "score only one decision class").option("--since <rfc3339>", "inclusive lower bound on when the decision was recorded").option("--until <rfc3339>", "exclusive upper bound on when the decision was recorded").option("--json", "output raw JSON").action(
|
|
2666
|
+
async (opts) => {
|
|
2667
|
+
try {
|
|
2668
|
+
const card = await new ErdoClient().decisionScorecard({
|
|
2669
|
+
workstream_slug: opts.workstream,
|
|
2670
|
+
source: opts.source,
|
|
2671
|
+
decision_class: opts.class,
|
|
2672
|
+
since: opts.since,
|
|
2673
|
+
until: opts.until
|
|
2674
|
+
});
|
|
2675
|
+
if (opts.json) {
|
|
2676
|
+
print(card);
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
renderScorecard(card);
|
|
2680
|
+
} catch (e) {
|
|
2681
|
+
fail(e);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
);
|
|
2440
2685
|
var approvalsCmd = program.command("approvals").description("List and decide approval requests (actions agents paused on)");
|
|
2441
2686
|
approvalsCmd.command("list").description("List approval requests, optionally filtered by status").option("-s, --status <status>", "pending | approved | rejected | expired (default: all)").option("-l, --limit <n>", "max requests", (v) => parseInt(v, 10)).option("--workstream <slug>", "only approvals for this workstream").option(
|
|
2442
2687
|
"--subject-type <type>",
|