tickmarkr 2.7.1 → 2.7.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.
@@ -7,7 +7,7 @@ import { basename, join } from "node:path";
7
7
  import { DEFAULT_EVIDENCE_QUOTA_BYTES } from "../config/config.js";
8
8
  import { dependencyLinkRefusal, DEFAULT_SHELL_TIMEOUT_MS, describeCapacity, sameCapacity, sh } from "../run/git.js";
9
9
  import { executionSignal } from "../run/execution-budget.js";
10
- import { isVitestTestCommand, manifestFileCount } from "./test-manifest.js";
10
+ import { isVitestTestCommand, manifestChildEnv, manifestFileCount } from "./test-manifest.js";
11
11
  export const EVIDENCE_TAIL_BYTES = 16 * 1024;
12
12
  /** The default per-run quota; `gates.evidenceQuotaBytes` in config overrides it (OBS-1140). */
13
13
  export const EVIDENCE_RUN_QUOTA_BYTES = DEFAULT_EVIDENCE_QUOTA_BYTES;
@@ -726,7 +726,11 @@ export async function captureBaseline(cwd, commands, opts = {}) {
726
726
  continue;
727
727
  }
728
728
  const evidence = beginGateEvidence(cwd, name, cmd, opts.evidence);
729
- const r = await sh(cmd, cwd, CAPTURE_CEILING_MS, { env: opts.evidence?.env, onReceipt: receipt => { evidence.observe(receipt); opts.onReceipt?.(receipt); } });
729
+ // Queue row 104 (D-1644): a manifested test command is captured under the test gate's own runner environment
730
+ // (node_modules/.bin first), so a bare repo-local runner resolves here exactly as it does in the gate — built on the
731
+ // caller's own evidence environment when it supplies one (D-1659), never in place of it.
732
+ const env = (name === "test" || name === "tipTest") && isVitestTestCommand(cmd, cwd) ? manifestChildEnv(cwd, opts.evidence?.env) : opts.evidence?.env;
733
+ const r = await sh(cmd, cwd, CAPTURE_CEILING_MS, { env, onReceipt: receipt => { evidence.observe(receipt); opts.onReceipt?.(receipt); } });
730
734
  (base.evidenceReceipts ??= {})[name] = evidence.finish(r.stdout, r.stderr);
731
735
  // ponytail: strip the executing cwd so repo-root capture and worktree compare fingerprint identically; /private-vs-/tmp symlink variance is out of scope
732
736
  // ponytail: a capture that was itself killed records the ceiling as its "measurement", which
@@ -15,11 +15,11 @@ import { disallowedBy, observedSeat } from "../route/preference.js";
15
15
  import { marginalCostRank } from "../route/router.js";
16
16
  import { carriedAuthorVendors, gateReviewerFloor, pickReviewer, reviewGate } from "./review.js";
17
17
  import { scopeGate } from "./scope.js";
18
- import { discoverTestManifest, evaluateManifestedTest, isVitestTestCommand, VITEST_CACHE_ENV, worktreeVitestCache } from "./test-manifest.js";
18
+ import { discoverTestManifest, evaluateManifestedTest, isVitestTestCommand, manifestChildEnv } from "./test-manifest.js";
19
19
  import { RUNNER_INFRA_DIAGNOSTIC_RE, timeoutShaped } from "./timeout-shaped.js";
20
20
  import { executionSignal } from "../run/execution-budget.js";
21
21
  import { failureDisposition } from "../run/recovery.js";
22
- import { dependencyLinkRefusal, FORK_CAP_ENV, preserveWorktree, producerFields, readCapacity, ROUTING_ENV_SEAMS, shGit, resolvedCapacity, sameCapacity, SUITE_PARENT_ENV, verificationProtocol } from "../run/git.js";
22
+ import { dependencyLinkRefusal, preserveWorktree, producerFields, readCapacity, shGit, resolvedCapacity, sameCapacity, verificationProtocol } from "../run/git.js";
23
23
  import { withJudgeInvocationEvidence } from "../run/journal.js";
24
24
  import { computeVerificationIdentity, verificationIdentityKey, formatReusedRow, getVerdictStore, isInfraResult, resolveStateDir, reusedIdentity, } from "./cache.js";
25
25
  const productionLoadProvider = () => loadavg()[0] ?? 0;
@@ -285,11 +285,7 @@ export function diagnosticAdmission(baseline, selected) {
285
285
  * discovery receives (test-manifest.ts manifestEnvironment), so it compares with the one a verdict
286
286
  * certified. Undefined when the runner cannot list — an unlisted manifest certifies nothing. */
287
287
  async function listFullManifest(cmd, worktree) {
288
- const env = { ...process.env, PATH: `${join(worktree, "node_modules/.bin")}:${process.env.PATH ?? ""}`,
289
- [VITEST_CACHE_ENV]: worktreeVitestCache(worktree),
290
- [FORK_CAP_ENV]: String(resolvedCapacity().forkCap), [SUITE_PARENT_ENV]: String(process.pid) };
291
- for (const key of [...ROUTING_ENV_SEAMS, "VITEST", "TEST", "VITEST_WORKER_ID", "VITEST_POOL_ID"])
292
- delete env[key];
288
+ const env = manifestChildEnv(worktree);
293
289
  const dir = mkdtempSync(join(tmpdir(), "tickmarkr-full-manifest-"));
294
290
  try {
295
291
  return (await discoverTestManifest(cmd, worktree, { dir, nonce: randomUUID(), env })).files;
@@ -206,6 +206,9 @@ export interface ManifestGateOutcome {
206
206
  exitCode: number;
207
207
  reportPath: string;
208
208
  }
209
+ /** Queue row 104 (D-1644): THE child environment of a manifested test command — its listing and run here, the full
210
+ * manifest re-listing (run-gates.ts) and its baseline capture (baseline.ts) — so every one resolves the same runner. */
211
+ export declare function manifestChildEnv(cwd: string, base?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
209
212
  export interface DiscoveredManifest {
210
213
  files: string[];
211
214
  listing: string;
@@ -504,17 +504,22 @@ export function runManifestedTest(cmd, cwd, opts) {
504
504
  * `npm_config_ignore_scripts` from the environment over every npmrc); the verdict records it so a
505
505
  * verdict measured with `pretest` hooks is never compared to one without. */
506
506
  function manifestEnvironment(cwd) {
507
- const env = { ...process.env, PATH: `${join(cwd, "node_modules/.bin")}:${process.env.PATH ?? ""}`,
507
+ const env = manifestBaseEnv(cwd);
508
+ return { env: scrubbedManifestEnv(env), verification: verificationProtocol(env, cwd) };
509
+ }
510
+ function manifestBaseEnv(cwd, base = process.env) {
511
+ return { ...base, PATH: `${join(cwd, "node_modules/.bin")}:${base.PATH ?? ""}`,
508
512
  [VITEST_CACHE_ENV]: worktreeVitestCache(cwd),
509
513
  [FORK_CAP_ENV]: String(resolvedCapacity().forkCap), [SUITE_PARENT_ENV]: String(process.pid) };
510
- const verification = verificationProtocol(env, cwd);
511
- for (const key of ROUTING_ENV_SEAMS)
512
- delete env[key];
513
- // A gate can itself be tested under Vitest. Do not give the child the outer worker identity.
514
- for (const key of Object.keys(env))
515
- if (["VITEST", "TEST", "VITEST_WORKER_ID", "VITEST_POOL_ID"].includes(key))
516
- delete env[key];
517
- return { env, verification };
514
+ }
515
+ // A gate can itself be tested under Vitest. Do not give the child the outer worker identity.
516
+ const OUTER_RUNNER_KEYS = new Set([...ROUTING_ENV_SEAMS, "VITEST", "TEST", "VITEST_WORKER_ID", "VITEST_POOL_ID"]);
517
+ const scrubbedManifestEnv = (env) => Object.fromEntries(Object.entries(env).filter(([key]) => !OUTER_RUNNER_KEYS.has(key)));
518
+ /** Queue row 104 (D-1644): THE child environment of a manifested test command — its listing and run here, the full
519
+ * manifest re-listing (run-gates.ts) and its baseline capture (baseline.ts) — so every one resolves the same runner. */
520
+ export function manifestChildEnv(cwd, base) {
521
+ // built on `base` when a caller supplies its own environment (captureBaseline's evidence env), else on process.env
522
+ return scrubbedManifestEnv(manifestBaseEnv(cwd, base));
518
523
  }
519
524
  /** OBS-1044: THE discovery seam — the files the runner would collect under this exact invocation,
520
525
  * from its own listing and never from a stdout summary. The gate and the baseline capture share it,
@@ -4,7 +4,7 @@ import { type DriverChoice } from "../drivers/index.js";
4
4
  import { type ExecutorDriver, type Slot } from "../drivers/types.js";
5
5
  import { type Baseline, type BaselineProvenance, type GateEvidenceOptions } from "../gates/baseline.js";
6
6
  import type { GateResult } from "../gates/types.js";
7
- import { type RunGraph } from "../graph/schema.js";
7
+ import { type RunGraph, type Task } from "../graph/schema.js";
8
8
  import { type PreserveProducer } from "./git.js";
9
9
  import { Journal, type JournalEvent } from "./journal.js";
10
10
  export declare function closeLiveSlot(liveSlots: Set<Slot>, driver: Pick<ExecutorDriver, "close">, slot: Slot): Promise<void>;
@@ -144,6 +144,19 @@ export declare const gateSatisfied: (g: GateResult) => boolean;
144
144
  * preference (ranked last, never excluded) — every review-pool-demotion except the launch-caused ones, which
145
145
  * never demote a seat (live or replayed). plan.ts previews a resume with this same fold. */
146
146
  export declare function replayedReviewerDemotions(events: readonly JournalEvent[]): Set<string>;
147
+ /** C: a journaled test red whose failing-file attribution is nonempty, trustworthy and complete (repair
148
+ * selection's own rule: a behavioral red on a real subject from a full suite or a screen that names
149
+ * every failure), POSITIVELY certified complete by its runner, uncontradicted by the files its own text
150
+ * names, and wholly outside a declared files[]. Empty files[] is unrestricted and never qualifies;
151
+ * every other shape keeps its replay. */
152
+ export declare function outsideScopeRed(t: Task, taskId: string, data: Record<string, unknown>, commit: string): boolean;
153
+ /** OBS-1106 residual, D-607: what ONE isolated rerun of a timeout-shaped red's attributed `files` says about that
154
+ * red. Lifted unchanged out of adjudicateInfraShapedRed (queue row 102) so its closed outcome table runs on this
155
+ * function in-process. Pure: it reads only its arguments; the caller classifies the rerun first. */
156
+ export declare function reobservationOutcome(files: readonly string[], rerun: GateResult): {
157
+ outcome: "reproduced" | "passed" | "ambiguous";
158
+ selection: string[] | undefined;
159
+ };
147
160
  /**
148
161
  * T4 (OBS-265): the journal with the review objections a round did NOT hinge on removed. Judge and
149
162
  * review are now launched together, so a round can journal a failed review that the serial walk would
@@ -409,7 +409,7 @@ const RUNNER_CERTIFICATE_RE = /runner-level diagnostic: never-started ([^;\s]+);
409
409
  * every failure), POSITIVELY certified complete by its runner, uncontradicted by the files its own text
410
410
  * names, and wholly outside a declared files[]. Empty files[] is unrestricted and never qualifies;
411
411
  * every other shape keeps its replay. */
412
- function outsideScopeRed(t, taskId, data, commit) {
412
+ export function outsideScopeRed(t, taskId, data, commit) {
413
413
  if (!t.files.length || data.gate !== "test" || data.pass !== false || data.skipped === true || data.commit !== commit
414
414
  // a failed producer — an infra row, a dirty-tree refusal, a recovery the runner could not finish —
415
415
  // never attributes anything, whatever files it lists
@@ -456,6 +456,21 @@ const observedSelection = (g) => {
456
456
  /** D-607: on the manifest path only the reporter's own attribution counts — a `FAIL path` line inside
457
457
  * captured stdout is text, not attribution. Headline lexing is for runners without a reporter. */
458
458
  const rerunFailingFiles = (g) => metaFiles(g.meta?.manifest) ? [...new Set(metaFiles(g.meta?.failingFiles) ?? [])].sort() : attributedFailingFiles(g);
459
+ /** OBS-1106 residual, D-607: what ONE isolated rerun of a timeout-shaped red's attributed `files` says about that
460
+ * red. Lifted unchanged out of adjudicateInfraShapedRed (queue row 102) so its closed outcome table runs on this
461
+ * function in-process. Pure: it reads only its arguments; the caller classifies the rerun first. */
462
+ export function reobservationOutcome(files, rerun) {
463
+ // Reproduced only by failure evidence the rerun ATTRIBUTES to one of exactly these files, from an
464
+ // OBSERVED selection that was exactly these files (D-607): a path merely printed in output, a
465
+ // positional filter that also collected another file, or a runner that ignored the file
466
+ // arguments and reported no manifest of its selection, says nothing about the original red.
467
+ const failing = rerunFailingFiles(rerun);
468
+ const selection = observedSelection(rerun);
469
+ const isolated = selection !== undefined && selection.length === files.length
470
+ && files.every((file) => selection.includes(file)) && failing.every((file) => files.includes(file));
471
+ const outcome = rerun.pass ? "passed" : isolated && !isInfraResult(rerun) && failing.length ? "reproduced" : "ambiguous";
472
+ return { outcome, selection };
473
+ }
459
474
  // v1.85 T3: the gates whose failure IS a deterministic measurement — a machine re-ran a command over a
460
475
  // tree and printed the same bytes. Those are the failures the fingerprint cap governs (the ruling names
461
476
  // it a "deterministic-gate" cap): a third identical answer to a question already answered twice is the
@@ -3080,15 +3095,9 @@ export async function runDaemon(repoRoot, opts = {}) {
3080
3095
  rerun = await withCommandContext(t.id, () => reobserveTestFiles(wt, commands.test, baseline, files, journal.dir))
3081
3096
  .catch((error) => ({ gate: "test", pass: false, details: `infra: rerun failed: ${error instanceof Error ? error.message : String(error)}`, meta: { infra: true } }));
3082
3097
  classifyInfraResult(rerun);
3083
- // Reproduced only by failure evidence the rerun ATTRIBUTES to one of exactly these files, from an
3084
- // OBSERVED selection that was exactly these files (D-607): a path merely printed in output, a
3085
- // positional filter that also collected another file, or a runner that ignored the file
3086
- // arguments and reported no manifest of its selection, says nothing about the original red.
3087
- const failing = rerunFailingFiles(rerun);
3088
- selection = observedSelection(rerun);
3089
- const isolated = selection !== undefined && selection.length === files.length
3090
- && files.every((file) => selection.includes(file)) && failing.every((file) => files.includes(file));
3091
- outcome = rerun.pass ? "passed" : isolated && !isInfraResult(rerun) && failing.length ? "reproduced" : "ambiguous";
3098
+ const judged = reobservationOutcome(files, rerun);
3099
+ outcome = judged.outcome;
3100
+ selection = judged.selection;
3092
3101
  }
3093
3102
  journal.append("gate-reobserved", t.id, {
3094
3103
  gate: "test", attempt, commit: subject, fingerprint, ...(observation ? { observation } : {}), files, outcome,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tickmarkr",
3
- "version": "2.7.1",
3
+ "version": "2.7.2",
4
4
  "description": "Spec in, verified work out.",
5
5
  "type": "module",
6
6
  "license": "MIT",