@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.
Files changed (2) hide show
  1. package/dist/index.js +246 -1
  2. 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>",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
4
  "description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {