@feigi/fleet-ctl 3.102.1 → 3.102.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feigi/fleet-ctl",
3
- "version": "3.102.1",
3
+ "version": "3.102.2",
4
4
  "description": "Agent fleet: run-team controller, merge bot, PR reviewer, and the ticket pipeline they share",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -98,6 +98,10 @@
98
98
  // that is missing, refuses. So does a record entry that names no finding
99
99
  // of the review, or a second entry for a finding an earlier entry
100
100
  // answers. A dimension that never ran does not: the note names it.
101
+ // - the Ruling itself is deriveRuling's, exported and pure: it takes the
102
+ // review's run and head, its findings and a record lookup, and reads no
103
+ // file. `append` reads the review and the record and ties them to the
104
+ // ledger, then calls it once.
101
105
  // The note reads `review <run> at <head7>, record <file>: <bucket>[<index>]
102
106
  // <charge> "<claim>"; …; none: <bucket>[<index>] <reason>, …; cleared:
103
107
  // survived[<index>] false-rationale, …; unrun: <dimension>, …`, and ` — <--note>`
@@ -130,7 +134,8 @@ const LEDGER_SCRIPT = join(SCRIPT_DIR, "ledger.mjs");
130
134
  // ---------------------------------------------------------------------------
131
135
  // pure core — exercised through the CLI by its tests; only
132
136
  // COLUMNS, LEGACY_WIDTH, TIER_SWITCH_DATE, parseTierOutcomes, rulingsByTicket,
133
- // rulingFor and lastPullByTicket are imported directly, the rest through argv
137
+ // rulingFor, lastPullByTicket and deriveRuling are imported directly, the rest
138
+ // through argv
134
139
  // ---------------------------------------------------------------------------
135
140
 
136
141
  // APPENDED TO, never inserted into: every awk read-out in the file's header
@@ -632,7 +637,7 @@ function recordFiles(scratch, pr) {
632
637
  .sort((a, b) => (a.letter < b.letter ? 1 : a.letter > b.letter ? -1 : 0));
633
638
  }
634
639
 
635
- // The last record answering review run `run`: `{file, record}`, `{missing}`
640
+ // The last record answering review run `run`: `{file, path, value}`, `{missing}`
636
641
  // naming why there is none, a retry's record replacing the first attempt's on
637
642
  // the same run and a record answering any other run changing nothing. A file
638
643
  // met before the answering one that cannot be read, or is not valid JSON,
@@ -649,7 +654,7 @@ function recordFor(scratch, pr, run) {
649
654
  const path = join(scratch, file);
650
655
  const read = readJson(path);
651
656
  if (read.problem !== undefined) return { missing: read.problem };
652
- if (read.value?.run === run) return { file, path, record: read.value };
657
+ if (read.value?.run === run) return { file, path, value: read.value };
653
658
  }
654
659
  return { missing: `no ${join(scratch, `dispositions-fix-pr-${pr}[-x].json`)} names review ${run} as the run it answers` };
655
660
  }
@@ -704,42 +709,28 @@ function chargesOf(review, record) {
704
709
  const FAILS_CLOSED = ["unmet", "both"];
705
710
  const MINTS = ["false-claim", "both"];
706
711
 
707
- // PR `pr`'s verdict columns and note off its first review's Charges:
708
- // `{closed_own_ticket, minted_false_claim, note}`, or `{missing}` saying what
709
- // is not there to read. `scratch` is `{root}` or `{unresolvable}`.
710
- function derivedRuling(pr, ledger, scratch) {
711
- const review = firstReview(prTokens(ledger?.rows ?? [], Number(pr)));
712
- if (review === null) {
713
- return { missing: `PR #${pr} has no first review on the ledger: no \`reviewed=\` token of its rows counted no survived and no unverified finding, or was answered by a fix-applier` };
714
- }
715
- if (scratch.unresolvable !== undefined) return { missing: `no scratch root to look in: ${scratch.unresolvable}` };
716
- const reviewPath = join(scratch.root, `pr${pr}`, review.run, "review.json");
717
- const read = readJson(reviewPath);
718
- if (read.code === "ENOENT") return { missing: `review ${review.run}'s result file ${reviewPath} is missing` };
719
- if (read.problem !== undefined) return { missing: read.problem };
720
- const file = read.value;
721
- const problem = reviewProblem(file);
722
- if (problem !== null) return { missing: `${reviewPath} ${problem}` };
723
- if (!sameHead(String(file.head).toLowerCase(), review.head)) {
724
- return { missing: `${reviewPath} is the review of head ${file.head}, not the ledger's review ${review.run} at ${review.head}` };
725
- }
726
- if (file.survived.length !== review.survived || file.unverified.length !== review.unverified) {
727
- return { missing: `${reviewPath} holds ${file.survived.length} survived and ${file.unverified.length} unverified, not the ${review.survived} and ${review.unverified} the ledger's review ${review.run} counted` };
728
- }
712
+ // The Ruling of a first review, pure: `{closed_own_ticket, minted_false_claim,
713
+ // note}`, or `{missing}` saying what is not there to read. `run` and `head` are
714
+ // the ledger token's; `review` is the findings as `review.json` holds them
715
+ // (`survived`, `refuted`, `unverified`, `dimensionsUnrun`), assumed well-formed
716
+ // as reviewProblem guarantees. `record` is called, at most once, only when a
717
+ // survived or unverified finding needs a record: it returns `{file, path,
718
+ // value}` or `{missing}`. Reads no file and runs no command.
719
+ export function deriveRuling({ run, head, review, record }) {
729
720
  // An entry that is neither a dimension's name nor {dimension} is named as
730
721
  // the JSON it is, so the note never reads as a blank dimension.
731
- const unrun = (Array.isArray(file.dimensionsUnrun) ? file.dimensionsUnrun : []).map((d) => oneLine(
722
+ const unrun = (Array.isArray(review.dimensionsUnrun) ? review.dimensionsUnrun : []).map((d) => oneLine(
732
723
  typeof d === "string" ? d : typeof d?.dimension === "string" ? d.dimension : JSON.stringify(d ?? null),
733
724
  ));
734
- const opening = `review ${review.run} at ${review.head.slice(0, 7)}`;
725
+ const opening = `review ${run} at ${head.slice(0, 7)}`;
735
726
  const noted = (parts) => (unrun.length === 0 ? parts : [...parts, `unrun: ${unrun.join(", ")}`]).join("; ");
736
727
 
737
- if (review.survived === 0 && review.unverified === 0) {
738
- return { review: review.run, closed_own_ticket: "yes", minted_false_claim: "no", note: `${opening}, no record: ${noted(["counted no survived and no unverified finding"])}` };
728
+ if (review.survived.length === 0 && review.unverified.length === 0) {
729
+ return { closed_own_ticket: "yes", minted_false_claim: "no", note: `${opening}, no record: ${noted(["counted no survived and no unverified finding"])}` };
739
730
  }
740
- const found = recordFor(scratch.root, pr, review.run);
731
+ const found = record();
741
732
  if (found.missing !== undefined) return { missing: found.missing };
742
- const charges = chargesOf(file, found.record);
733
+ const charges = chargesOf(review, found.value);
743
734
  if (charges.broken.length > 0) {
744
735
  return { missing: `${found.path} has ${charges.broken.length === 1 ? "an entry" : "entries"} a Ruling cannot read: ${charges.broken.join("; ")}` };
745
736
  }
@@ -753,10 +744,34 @@ function derivedRuling(pr, ledger, scratch) {
753
744
  closed_own_ticket: charges.charged.some((c) => FAILS_CLOSED.includes(c.charge)) ? "no" : "yes",
754
745
  minted_false_claim: charges.charged.some((c) => MINTS.includes(c.charge)) ? "yes" : "no",
755
746
  note: `${opening}, record ${found.file}: ${noted(parts.length === 0 ? ["no finding stands"] : parts)}`,
756
- review: review.run,
757
747
  };
758
748
  }
759
749
 
750
+ // PR `pr`'s first review read from scratch and tied to the ledger, then ruled
751
+ // by deriveRuling: its Ruling plus `review`, the review's run, or `{missing}`
752
+ // saying what is not there to read. `scratch` is `{root}` or `{unresolvable}`.
753
+ function scratchRuling(pr, ledger, scratch) {
754
+ const review = firstReview(prTokens(ledger?.rows ?? [], Number(pr)));
755
+ if (review === null) {
756
+ return { missing: `PR #${pr} has no first review on the ledger: no \`reviewed=\` token of its rows counted no survived and no unverified finding, or was answered by a fix-applier` };
757
+ }
758
+ if (scratch.unresolvable !== undefined) return { missing: `no scratch root to look in: ${scratch.unresolvable}` };
759
+ const reviewPath = join(scratch.root, `pr${pr}`, review.run, "review.json");
760
+ const read = readJson(reviewPath);
761
+ if (read.code === "ENOENT") return { missing: `review ${review.run}'s result file ${reviewPath} is missing` };
762
+ if (read.problem !== undefined) return { missing: read.problem };
763
+ const file = read.value;
764
+ const problem = reviewProblem(file);
765
+ if (problem !== null) return { missing: `${reviewPath} ${problem}` };
766
+ if (!sameHead(String(file.head).toLowerCase(), review.head)) {
767
+ return { missing: `${reviewPath} is the review of head ${file.head}, not the ledger's review ${review.run} at ${review.head}` };
768
+ }
769
+ if (file.survived.length !== review.survived || file.unverified.length !== review.unverified) {
770
+ return { missing: `${reviewPath} holds ${file.survived.length} survived and ${file.unverified.length} unverified, not the ${review.survived} and ${review.unverified} the ledger's review ${review.run} counted` };
771
+ }
772
+ return { ...deriveRuling({ run: review.run, head: review.head, review: file, record: () => recordFor(scratch.root, pr, review.run) }), review: review.run };
773
+ }
774
+
760
775
  // The step a refusal hands the controller. A merged PR's evidence is
761
776
  // reclaimed with its scratch, so the verdicts are given by hand; any other
762
777
  // PR's is still wanted, and a ruling is never typed for it.
@@ -831,7 +846,7 @@ function append(pr, paths) {
831
846
  die(`PR #${pr} is ${state === "OPEN" ? "open" : "closed without merging"}, and verdicts are given by hand only for a merged PR — leave out --closed-own-ticket and --minted-false-claim, and \`append\` works them out from the record: ${nextStep(pr, state)}`);
832
847
  }
833
848
  } else {
834
- const ruled = derivedRuling(pr, ledger, scratch);
849
+ const ruled = scratchRuling(pr, ledger, scratch);
835
850
  if (ruled.missing !== undefined) {
836
851
  let state = null;
837
852
  let unread = "";