diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index d71fcec..72644d5 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -17,14 +17,15 @@ Track only **active, reproducible, unresolved** issues. ## Active issues ### `test/scheduler-preflight-lock.e2e.test.mjs` platform mismatch (`unknown` vs `codex`) -- **Status:** Resolved +- **Status:** Active +- **Issue-ID:** KNOWN-ISSUE-scheduler-preflight-platform - **Area:** Test -- **Symptom:** Historical issue where test expected `platform: unknown` while scheduler logs used `platform: codex`. -- **Trigger/Conditions:** Previously reproed in Codex. -- **Workaround:** None needed after assertion behavior update. -- **Impact:** No longer blocking. +- **Symptom:** The first e2e scenario fails because expected frontmatter platform resolves to `unknown` in test context while scheduler output persists `platform: codex`. +- **Trigger/Conditions:** Running `node test/scheduler-preflight-lock.e2e.test.mjs` or `node --test test/scheduler-preflight-lock.e2e.test.mjs` in this environment. +- **Workaround:** Exclude `test/scheduler-preflight-lock.e2e.test.mjs` from local sandbox triage runs until the expectation source is aligned with scheduler platform semantics. +- **Impact:** Medium — scheduler e2e bundle reports a deterministic failure unrelated to most feature changes. - **Related notes:** `docs/agent-handoffs/incidents/2026-02-20-codex-test-failures-platform-and-telemetry.md` -- **Last verified:** 2026-02-20 +- **Last verified:** 2026-02-21 ### `test/memory-telemetry.test.mjs` expects child-process stdout/stderr that are empty in this environment - **Status:** Active diff --git a/TORCH.md b/TORCH.md index 72b1357..7247e71 100644 --- a/TORCH.md +++ b/TORCH.md @@ -330,6 +330,9 @@ Common settings: - `dashboard.hashtag` — custom hashtag for lock events (defaults to `-agent-lock`). - `scheduler.firstPromptByCadence.daily` / `.weekly` — first-run scheduler starting agent. - `scheduler.handoffCommandByCadence.daily` / `.weekly` — shell command run after lock acquisition; command must use `SCHEDULER_AGENT`, `SCHEDULER_CADENCE`, and `SCHEDULER_PROMPT_PATH` provided by `scripts/agent/run-scheduler-cycle.mjs`. +- `scheduler.handoffPolicyByCadence.daily` / `.weekly` — optional retry/fallback policy for handoff execution (`maxAttempts`, `retryBackoffMs`, `retryOnPatterns`, `fallbackPlatform`). +- `scheduler.runnerHealthCommandByCadence.daily` / `.weekly` — optional command run before lock acquisition to verify runner availability; non-zero exits fail the run before prompt execution. +- `scheduler.memoryPolicyByCadence..retrieveArtifacts` / `.storeArtifacts` — evidence file list; recommended canonical files include `.scheduler-memory/latest//retrieve.ok`, `.scheduler-memory/latest//retrieve.json`, `.scheduler-memory/latest//store.ok`, `.scheduler-memory/latest//store.json`. - `scheduler.paused.daily` / `.weekly` — array of agent names to exclude from scheduler rotation. - `scheduler.strict_lock` — lock backend policy switch (default: `true`); when `false`, scheduler defers backend-unavailable lock failures before converting the run to failed. - `scheduler.degraded_lock_retry_window` — non-strict deferral window in milliseconds; backend lock failures outside this window immediately consume failure budget and mark run failed. diff --git a/docs/agent-handoffs/incidents/2026-02-20-codex-test-failures-platform-and-telemetry.md b/docs/agent-handoffs/incidents/2026-02-20-codex-test-failures-platform-and-telemetry.md index cfafaee..b95ce41 100644 --- a/docs/agent-handoffs/incidents/2026-02-20-codex-test-failures-platform-and-telemetry.md +++ b/docs/agent-handoffs/incidents/2026-02-20-codex-test-failures-platform-and-telemetry.md @@ -28,3 +28,30 @@ - Treat these as environment-sensitive test failures until assertions are aligned with Codex platform semantics and child-process output behavior. - If updating tests, follow spec-correction protocol and preserve strict behavioral intent. - Keep `KNOWN_ISSUES.md` dates/status current after remediation. + +## Update 2026-02-21 + +### Context +- Agent: `test-audit-agent` +- Scope: scenario-first audit run using `scripts/test-audit/run-flaky-check.mjs` and targeted scheduler e2e verification. + +### Observation +- The scheduler platform mismatch is still reproducible: + - Expected in test assertion: `platform: unknown` (derived from test-side `detectPlatform() || 'unknown'`) + - Actual log frontmatter: `platform: codex` +- Reproduced with: + - `node test/scheduler-preflight-lock.e2e.test.mjs` + - `node --test test/scheduler-preflight-lock.e2e.test.mjs` + +### Action taken +- Reclassified the issue in `KNOWN_ISSUES.md` from Resolved back to Active (`KNOWN-ISSUE-scheduler-preflight-platform`) with updated workaround and verification date. +- Did not weaken/modify scenario expectations in the e2e test without explicit spec correction. + +### Validation performed +- `node test/scheduler-preflight-lock.e2e.test.mjs` (fails with platform mismatch) +- `node --test test/scheduler-preflight-lock.e2e.test.mjs` (fails) + +### Recommendation for next agents +- Resolve via spec correction only: + 1. Decide canonical platform source for scheduler logs in fixtures (`AGENT_PLATFORM` fallback vs runtime detector). + 2. Update assertion to match documented scheduler behavior, not incidental environment state. diff --git a/docs/proposals/plan-agent-architecture.md b/docs/proposals/plan-agent-architecture.md new file mode 100644 index 0000000..c8dcc99 --- /dev/null +++ b/docs/proposals/plan-agent-architecture.md @@ -0,0 +1,542 @@ +# Proposal: Plan Agent Architecture — Closing the Idea-to-Implementation Gap + +## Problem Statement + +TORCH has **44 agents** generating proposals, audits, and feature ideas — but **zero agents** that evaluate those proposals, plan implementation strategies, or build approved features. The result is a growing pile of dead ends: + +### Diagnosed Dead Ends (Current State) + +| Dead End | Type | Why It's Stuck | +|----------|------|----------------| +| Node-based prompt editor | Proposal | 306-line design doc, zero implementation, no champion | +| Prompt packages system | Proposal | Concept doc only, never prioritized into work | +| Hybrid distribution (binary + dashboard) | Proposal | Architecture written, zero progress | +| `event-schema-agent` | Broken agent | References `src/lib/eventSchemas.js` which doesn't exist — failed 2x, never fixed | +| `content-audit-agent` | Scope mismatch | Audits upload/contribution features that don't exist in TORCH | +| `style-agent` | Missing tooling | Blocked on missing `npm run format` script — never added | +| 7 proposed agents in `AGENT_COVERAGE_GAPS.md` | Coverage gaps | Documented but no agent builds new agents | +| 77 scattered TODO files | Organizational debt | Created daily, never consolidated or triaged | +| 130+ dead artifacts | Audit fatigue | Reports generated continuously, never acted upon | + +### Root Cause: Missing Pipeline Stages + +The current pipeline has a structural gap. Ideas flow in but never flow through to completion: + +``` +CURRENT PIPELINE (broken feedback loop) + + feature-proposer-agent ──→ creates single file in features/ + prompt-gap-analysis-agent ──→ identifies gaps, writes report + weekly-synthesis-agent ──→ summarizes activity + governance-agent ──→ applies/rejects prompt changes + + ┌─────────────────────────────────────────────────┐ + │ MISSING: Who evaluates proposals? │ + │ MISSING: Who plans implementation strategy? │ + │ MISSING: Who builds multi-file features? │ + │ MISSING: Who retires dead-end work? │ + │ MISSING: Who closes the loop on artifacts? │ + └─────────────────────────────────────────────────┘ +``` + +The `feature-proposer-agent` creates single-file features in `features/`. It explicitly cannot modify existing files or execute multi-step implementations. The `governance-agent` handles prompt changes but not feature development. Nobody triages the 3 stalled proposals, 7 unimplemented agent gaps, or 15 failed task logs. + +--- + +## Proposed Solution: Three New Pipeline Agents + +Add three new weekly agents that close the gap between "idea" and "implementation": + +``` +PROPOSED PIPELINE (closed loop) + + IDEAS IN BUILT OUT + ───────── ───────── + feature-proposer-agent ─┐ + prompt-gap-analysis-agent ─┤ + docs/proposals/*.md ──────┤ + AGENT_COVERAGE_GAPS.md ───┤ + KNOWN_ISSUES.md ──────────┤ + artifacts/ reports ───────┘ + │ + ┌───────▼────────┐ + │ proposal- │ TRIAGE: Evaluates all pending + │ triage-agent │ proposals, dead ends, and gaps. + │ │ Produces prioritized backlog. + └───────┬────────┘ + │ + ┌───────▼────────┐ + │ plan-agent │ PLAN: Takes top-priority item + │ │ from backlog. Produces step-by-step + │ │ implementation plan with file list, + │ │ test strategy, and acceptance criteria. + └───────┬────────┘ + │ + ┌───────▼────────┐ + │ builder-agent │ BUILD: Executes approved plan. + │ │ Creates/modifies files, writes tests, + │ │ validates against acceptance criteria. + └────────────────┘ +``` + +### Why Three Agents, Not One? + +A single "do everything" agent would violate TORCH's core design principles: + +1. **Single responsibility** — Each agent has one job, one prompt, one failure mode +2. **Round-robin fairness** — Three separate agents get three separate time slots; a monolithic agent would monopolize an entire weekly slot +3. **Incremental progress** — Triage can run without planning; planning can run without building; each produces value independently +4. **Auditable handoffs** — Each stage produces artifacts that the next stage consumes, creating a reviewable paper trail +5. **Failure isolation** — If the builder fails, the plan still exists for the next cycle; if triage fails, existing plans aren't affected + +--- + +## Agent 1: `proposal-triage-agent` (Weekly) + +### Identity + +> You are: **proposal-triage-agent**, a weekly agent that evaluates pending proposals, identifies dead ends, and produces a prioritized implementation backlog. + +### Mission + +Synthesize all pending work items (proposals, coverage gaps, dead-end agents, stale artifacts) into a single prioritized backlog file that downstream agents can consume. + +### Inputs (Read) + +| Source | What to Extract | +|--------|----------------| +| `docs/proposals/*.md` | Active proposals and their implementation status | +| `docs/AGENT_COVERAGE_GAPS.md` | Proposed but unimplemented agents | +| `KNOWN_ISSUES.md` | Blocking issues that need tooling fixes | +| `task-logs/daily/` + `task-logs/weekly/` | Failed agent runs (pattern detection) | +| `artifacts/` | Reports that haven't driven action | +| `src/todo/` | Scattered TODO files needing consolidation | +| `features/` | Single-file features from `feature-proposer-agent` | +| `docs/agent-handoffs/incidents/` | Unresolved incidents | +| Previous backlog (`src/backlog/BACKLOG.md`) | Carry forward items not yet addressed | + +### Output (Write) + +**Primary artifact:** `src/backlog/BACKLOG.md` + +```markdown +# Implementation Backlog — + +## Triage Summary +- Items evaluated: +- New items added: +- Items retired (dead end): +- Items promoted to ready: + +## Priority 1: Ready to Plan + + +### BACKLOG-001: Add `npm run format` script to package.json +- **Source:** KNOWN_ISSUES.md, style-agent failures +- **Impact:** Unblocks style-agent (daily cadence) +- **Scope:** Single line in package.json +- **Status:** ready-to-plan +- **Acceptance:** `npm run format` exits 0; style-agent completes successfully + +### BACKLOG-002: Create `src/lib/eventSchemas.js` +- **Source:** event-schema-agent failures (2x) +- **Impact:** Unblocks event-schema-agent (weekly cadence) +- **Scope:** Extract event builders from `src/nostr-lock.mjs` into shared module +- **Status:** ready-to-plan +- **Acceptance:** event-schema-agent completes; `src/nostr-lock.mjs` imports from new module + +## Priority 2: Needs Scoping + + +### BACKLOG-003: Implement Phase 0 of prompt graph extraction +- **Source:** docs/proposals/node-based-prompt-editor.md +- **Impact:** Machine-readable agent metadata for all 44 prompts +- **Scope:** 4 new scripts in `scripts/prompt-graph/` +- **Status:** needs-scoping +- **Blockers:** None +- **Open Questions:** Regex patterns may not cover all prompt variations + +## Priority 3: Deferred + + +## Retired (Dead Ends) + + +### RETIRED: content-audit-agent upload/contribution scope +- **Rationale:** TORCH has no upload UI, API, or storage. Agent audits features + that don't exist. Recommend revising agent scope instead of building features. +``` + +### Triage Criteria + +Items are scored on four dimensions: + +| Dimension | Weight | Scoring | +|-----------|--------|---------| +| **Unblock** | 3x | Does this unblock a currently-broken agent or workflow? | +| **Value** | 2x | Does this deliver user-visible value or reduce failure rate? | +| **Scope** | 1x | Is the implementation scope clear and bounded? | +| **Risk** | -1x | Does this require architectural changes or new dependencies? | + +### Retirement Rules + +An item should be retired (not deferred) when: +- The feature it depends on doesn't exist and isn't planned (e.g., content-audit-agent auditing upload features) +- The proposal has been superseded by a different approach +- The cost clearly exceeds the benefit with no path to improvement +- The item has been deferred for 4+ consecutive triage cycles with no progress + +### Weekly Workflow + +1. Read all inputs listed above +2. Read previous `src/backlog/BACKLOG.md` if it exists +3. Evaluate each pending item against triage criteria +4. Score and rank items +5. Write updated `src/backlog/BACKLOG.md` +6. Write triage summary to `src/decisions/DECISIONS_.md` + +--- + +## Agent 2: `plan-agent` (Weekly) + +### Identity + +> You are: **plan-agent**, a weekly agent that creates detailed implementation plans for the highest-priority backlog item. + +### Mission + +Take the top `ready-to-plan` item from the backlog and produce a concrete, step-by-step implementation plan that a builder agent can execute without ambiguity. + +### Inputs (Read) + +| Source | What to Extract | +|--------|----------------| +| `src/backlog/BACKLOG.md` | Top `ready-to-plan` item | +| Original proposal/source doc | Full context for the item | +| Relevant codebase files | Current implementation to understand integration points | +| `KNOWN_ISSUES.md` | Active blockers that might affect the plan | +| `docs/agent-handoffs/learnings/` | Past learnings relevant to this work | +| Previous plans (`src/plans/`) | Avoid re-planning already-planned items | + +### Output (Write) + +**Primary artifact:** `src/plans/PLAN-.md` + +```markdown +# Implementation Plan: BACKLOG-001 — Add `npm run format` script + +## Backlog Reference +- **ID:** BACKLOG-001 +- **Source:** KNOWN_ISSUES.md, style-agent failures +- **Priority:** P1 (unblocks daily agent) + +## Objective +Add a working `npm run format` script to `package.json` that the +`style-agent` can invoke to auto-format code. + +## Analysis +- `style-agent` prompt references `npm run format` at line 12 +- `package.json` has `prettier` in devDependencies (v3.2.x) +- `.prettierrc` exists with project configuration +- No existing format script; only `lint` exists + +## Implementation Steps + +### Step 1: Add format script to package.json +- **File:** `package.json` +- **Change:** Add `"format": "prettier --write ."` to `scripts` object +- **Test:** `npm run format` exits 0 without errors + +### Step 2: Add format check script (CI-friendly) +- **File:** `package.json` +- **Change:** Add `"format:check": "prettier --check ."` to `scripts` object +- **Test:** `npm run format:check` exits 0 after formatting + +### Step 3: Validate style-agent can complete +- **Action:** Verify `style-agent` prompt references match available scripts +- **Test:** Dry-run style-agent workflow mentally against available npm scripts + +## Files Modified +- `package.json` (2 lines added to `scripts`) + +## Files Created +- None + +## Acceptance Criteria +1. `npm run format` exits 0 and formats `.js` / `.mjs` files per `.prettierrc` +2. `npm run format:check` exits 0 after running `npm run format` +3. `style-agent` prompt commands all resolve to valid npm scripts +4. No existing tests break + +## Test Strategy +- Run `npm run format` and verify exit code +- Run `npm run format:check` and verify exit code +- Run `npm test` to verify no regressions +- Run `npm run lint` to verify compatibility + +## Risk Assessment +- **Low risk:** Adding scripts to package.json is additive +- **No architectural changes required** +- **Rollback:** Remove the two script lines + +## Dependencies +- None — self-contained change + +## Estimated Complexity +- **Simple** (< 5 file changes, no new modules) +``` + +### Planning Rules + +1. **One plan per cycle** — Don't try to plan everything. Pick the single highest-priority `ready-to-plan` item +2. **Concrete file paths** — Every step must name the exact file(s) to create or modify +3. **Testable acceptance** — Every acceptance criterion must be verifiable by running a command +4. **No ambiguity** — A builder agent reading only this plan should be able to implement it without guessing +5. **Scope guard** — If the item is too large for a single builder cycle, break it into sub-plans and update the backlog + +### Status Updates + +After planning, update the backlog item's status: + +``` +- **Status:** ready-to-plan → planned (see src/plans/PLAN-BACKLOG-001.md) +``` + +### Weekly Workflow + +1. Read `src/backlog/BACKLOG.md` +2. Select top `ready-to-plan` item +3. If no items are `ready-to-plan`, check for `needs-scoping` items and attempt to scope them (update status to `ready-to-plan` if successful) +4. Read all referenced source documents and relevant codebase files +5. Write implementation plan to `src/plans/PLAN-.md` +6. Update backlog item status to `planned` +7. Write planning rationale to `src/decisions/DECISIONS_.md` + +--- + +## Agent 3: `builder-agent` (Weekly) + +### Identity + +> You are: **builder-agent**, a weekly agent that implements the oldest approved plan from the plans directory. + +### Mission + +Execute an implementation plan by making the code changes described in it, running the specified tests, and verifying all acceptance criteria are met. + +### Inputs (Read) + +| Source | What to Extract | +|--------|----------------| +| `src/plans/PLAN-*.md` | Oldest plan with status `planned` (not `completed` or `in-progress`) | +| All files referenced in the plan | Current state of code to be modified | +| `KNOWN_ISSUES.md` | Active blockers that might affect implementation | +| `src/backlog/BACKLOG.md` | Context for the work item | + +### Implementation Protocol + +1. **Claim the plan** — Add frontmatter `status: in-progress` and `started: ` to the plan file +2. **Execute steps sequentially** — Follow the plan's implementation steps in order +3. **Validate after each step** — Run the step's test command if specified +4. **Run acceptance criteria** — Execute every acceptance criterion command +5. **Update plan status** — Set `status: completed` with `completed: ` if all criteria pass +6. **Update backlog** — Set backlog item status to `completed` +7. **Handle failures** — If any acceptance criterion fails: + - Set plan status to `blocked` with `blocked_reason` + - Do NOT mark backlog item as completed + - Document the failure in `KNOWN_ISSUES.md` if it's a new issue + - Write incident note to `docs/agent-handoffs/incidents/` + +### Output (Write) + +- Modified/created files per the plan +- Updated plan file (status, timestamps) +- Updated backlog (item status) +- Test log in `src/test_logs/TEST_LOG_.md` + +### Builder Rules + +1. **Follow the plan exactly** — Don't improvise. If the plan says "add line X to file Y", do that +2. **Don't expand scope** — Only make changes listed in the plan. No bonus refactors, no "while I'm here" improvements +3. **Fail loudly** — If a step can't be completed, stop and document why. Don't skip steps +4. **Test everything** — Run every test command in the plan. Run `npm test` and `npm run lint` as final validation +5. **One plan per cycle** — Complete one plan fully before starting another + +### Conflict with `feature-proposer-agent` + +The existing `feature-proposer-agent` creates single-file features in `features/`. The `builder-agent` executes multi-step plans that may modify existing files. These agents have different scopes: + +| Aspect | feature-proposer-agent | builder-agent | +|--------|----------------------|---------------| +| Scope | Single new file in `features/` | Any files specified in plan | +| Input | Own analysis of codebase | Explicit plan from `plan-agent` | +| Modifications | New files only | New + existing files | +| Validation | Self-verified | Acceptance criteria from plan | +| Autonomy | High (chooses what to build) | Low (follows plan exactly) | + +Both agents should continue to exist. The `feature-proposer-agent` handles quick, self-contained additions. The `builder-agent` handles planned, multi-step implementations. + +--- + +## Integration with Existing Pipeline + +### Roster Changes + +Add to `src/prompts/roster.json` under `weekly`: + +```json +{ + "weekly": [ + "bug-reproducer-agent", + "builder-agent", + "changelog-agent", + "dead-code-agent", + "feature-proposer-agent", + "frontend-console-debug-agent", + "fuzz-agent", + "perf-deepdive-agent", + "perf-optimization-agent", + "plan-agent", + "pr-review-agent", + "prompt-fixer-agent", + "prompt-gap-analysis-agent", + "prompt-maintenance-agent", + "prompt-safety-agent", + "proposal-triage-agent", + "race-condition-agent", + "refactor-agent", + "repo-fit-agent", + "smoke-agent", + "telemetry-agent", + "test-coverage-agent", + "ui-ux-agent", + "weekly-synthesis-agent" + ] +} +``` + +### New Directories + +``` +src/backlog/ # Prioritized implementation backlog (from proposal-triage-agent) +src/plans/ # Implementation plans (from plan-agent) +``` + +### New Prompt Files + +``` +src/prompts/weekly/proposal-triage-agent.md +src/prompts/weekly/plan-agent.md +src/prompts/weekly/builder-agent.md +``` + +### Execution Order Consideration + +In the current round-robin, these agents run in alphabetical order within the weekly cadence. The ideal execution order is: + +1. `proposal-triage-agent` (produces backlog) +2. `plan-agent` (consumes backlog, produces plan) +3. `builder-agent` (consumes plan, produces implementation) + +Since round-robin is alphabetical (`builder-agent` < `plan-agent` < `proposal-triage-agent`), the builder would run *before* the planner in any given week. This is acceptable because: + +- Plans persist across weeks — a plan written in week N is built in week N+1 +- The pipeline is designed for multi-week cadence, not single-week completion +- Each agent checks for existing artifacts from previous cycles + +If same-week execution is desired in the future, the Phase 3+ custom flow configurations from the prompt editor proposal would enable DAG-based ordering. + +### Interaction with Existing Agents + +| Existing Agent | Interaction | +|---------------|-------------| +| `feature-proposer-agent` | Features it creates may be evaluated by `proposal-triage-agent` | +| `prompt-gap-analysis-agent` | Gap reports feed into `proposal-triage-agent` inputs | +| `weekly-synthesis-agent` | Summarizes triage/plan/build activity in weekly report | +| `governance-agent` | Prompt changes from builder go through normal governance | +| `prompt-maintenance-agent` | Validates new agent prompts follow contract | +| `dead-code-agent` | May identify retired items for triage to confirm | + +--- + +## Addressing the Identified Dead Ends + +Here's how the new pipeline would handle each current dead end: + +| Dead End | Agent | Action | +|----------|-------|--------| +| Missing `npm run format` | triage → plan → builder | P1: triage identifies, plan writes 2-line change, builder implements | +| Missing `eventSchemas.js` | triage → plan → builder | P1: triage identifies, plan designs extraction, builder creates module | +| content-audit-agent scope | triage | Retired: agent audits nonexistent features | +| Prompt editor Phase 0 | triage → plan | Scoped: triage breaks into sub-tasks, plan designs Phase 0 extraction | +| Prompt packages | triage → plan | Deferred or scoped depending on priority vs. other items | +| Distribution binary | triage | Deferred: requires external tooling, low relative priority | +| 7 coverage gap agents | triage → plan → builder | Each gap becomes a backlog item; plan designs prompt, builder creates it | +| 77 scattered TODOs | triage | Consolidated into backlog; triage replaces scattered TODO creation | +| 130+ dead artifacts | triage | Evaluated for actionability; non-actionable artifacts flagged for GC | + +--- + +## Success Metrics + +| Metric | Current | Target (4 weeks) | Target (8 weeks) | +|--------|---------|-------------------|-------------------| +| Stalled proposals | 3 | 2 (1 retired, 1 planned) | 0 (all planned or retired) | +| Broken agents | 3 (event-schema, style, content-audit) | 1 (content-audit retired) | 0 | +| Unimplemented coverage gaps | 7 | 5 (2 planned) | 3 (2 built, 2 planned) | +| Backlog items with clear status | 0 | 10+ | 15+ | +| Plans executed per month | 0 | 2 | 4 | + +--- + +## Implementation Plan for This Proposal + +This proposal itself should go through the pipeline it describes. Bootstrap order: + +### Step 1: Create directories +```bash +mkdir -p src/backlog src/plans +``` + +### Step 2: Create `proposal-triage-agent.md` +- Write prompt following shared agent run contract +- Add to `roster.json` weekly array + +### Step 3: Create `plan-agent.md` +- Write prompt following shared agent run contract +- Add to `roster.json` weekly array + +### Step 4: Create `builder-agent.md` +- Write prompt following shared agent run contract +- Add to `roster.json` weekly array + +### Step 5: Seed initial backlog +- Create `src/backlog/BACKLOG.md` with known dead ends pre-triaged +- This gives the plan-agent something to work with on its first run + +### Step 6: Validate +- Run `node scripts/validate-prompt-contract.mjs` +- Run `node scripts/validate-scheduler-roster.mjs` +- Run `npm run lint` +- Run `npm test` + +--- + +## Risks + +| Risk | Severity | Mitigation | +|------|----------|------------| +| Builder agent makes incorrect changes | High | Plan must have testable acceptance criteria; builder runs all tests | +| Triage agent deprioritizes important work | Medium | Triage criteria are explicit and weighted; human can override backlog | +| Plan scope creep | Medium | Planning rules enforce one plan per cycle, concrete file paths | +| Agents produce circular work (triage creates items that triage later retires) | Low | Retirement rules require 4+ deferred cycles before retirement | +| Builder conflicts with other agents modifying same files | Medium | Lock system prevents concurrent modification; plan specifies exact files | + +--- + +## Relationship to Other Proposals + +- **Prompt Editor (Phase 0):** The triage agent would evaluate this proposal and potentially break Phase 0 into backlog items. The plan agent would design the extraction scripts. The builder agent would create them. +- **Prompt Packages:** Same pipeline — triage evaluates, plan designs, builder implements. +- **Distribution Plan:** Triage would likely defer this (requires external tooling) but keep it visible in the backlog. +- **Agent Coverage Gaps:** Each gap becomes a backlog item. The builder agent is literally designed to create new agent prompts from plans. diff --git a/memory-updates/2026-02-21T19-48-22Z__test-audit-agent.md b/memory-updates/2026-02-21T19-48-22Z__test-audit-agent.md new file mode 100644 index 0000000..c1ab2ff --- /dev/null +++ b/memory-updates/2026-02-21T19-48-22Z__test-audit-agent.md @@ -0,0 +1,13 @@ +# Memory Update - test-audit-agent - 2026-02-21 + +## Key findings +- `scripts/test-audit/run-flaky-check.mjs` can produce false-green empty output when relying on piped child stdout in this environment. +- Writing node:test TAP output to explicit reporter destination files is stable across this sandbox and keeps flakiness evidence auditable. + +## Patterns / reusable knowledge +- For audit tooling that consumes child test output, prefer file-based reporter destinations over stdio capture. +- Add deterministic pass/fail fixtures to test audit scripts so regressions in signal extraction fail immediately. + +## Warnings / gotchas +- `test/scheduler-preflight-lock.e2e.test.mjs` remains reproducibly red on platform mismatch (`unknown` vs `codex`) and should be treated as active until spec correction. +- Running node:test from inside another node:test process may require clearing `NODE_TEST_CONTEXT` in child env for stable behavior. diff --git a/memory-updates/2026-02-21T20-08-13Z__scheduler-update-agent.md b/memory-updates/2026-02-21T20-08-13Z__scheduler-update-agent.md new file mode 100644 index 0000000..d2cc9bc --- /dev/null +++ b/memory-updates/2026-02-21T20-08-13Z__scheduler-update-agent.md @@ -0,0 +1,13 @@ +# Memory Update - scheduler-update-agent - 2026-02-21 + +## Key findings +- Scheduler reliability improves when handoff failures are classified and retried only for retryable network signatures. +- `lock:check --json --quiet` still needs defensive parsing; consume structured JSON events and choose the payload with exclusion keys. + +## Patterns / reusable knowledge +- For spawned-agent handoff, use bounded retries with backoff plus optional fallback platform as a last resort. +- For runner availability, run a preflight command before lock acquisition to avoid claiming work that cannot execute. + +## Warnings / gotchas +- `cmdCheck` should suppress query health/error loggers in quiet JSON mode to keep automation parsing deterministic. +- Memory evidence is easier to validate when latest cadence artifacts include both marker and JSON (`retrieve/store.ok` + `retrieve/store.json`). diff --git a/memory-updates/2026-02-21T20-22-32Z__proposal-triage-agent.md b/memory-updates/2026-02-21T20-22-32Z__proposal-triage-agent.md new file mode 100644 index 0000000..d6ed359 --- /dev/null +++ b/memory-updates/2026-02-21T20-22-32Z__proposal-triage-agent.md @@ -0,0 +1,13 @@ +# Memory Update — proposal-triage-agent — 2026-02-21 + +## Key findings +- The highest-value unblockers remain local and deterministic: missing `npm run format`, missing `src/lib/eventSchemas.js`, and scheduler preflight platform assertion drift. +- Coverage-gap ideas should be split by urgency; low-priority agent additions dilute execution if treated as near-term work. + +## Patterns / reusable knowledge +- Keep backlog focused on `ready-to-plan` items that remove recurring scheduler/test friction before adding net-new agent surface area. +- Retire upstream-only defects from implementation backlog, but retain them in known-issues/incidents for operator visibility. + +## Warnings / gotchas +- `../scheduler-flow.md` links in weekly prompt files resolve to `src/prompts/scheduler-flow.md`; direct parent-relative lookups from repo root can appear missing during manual checks. +- Artifact-verifier failures often stem from metadata/frontmatter omissions rather than code defects; triage should classify these separately. diff --git a/reports/test-audit/flakiness-matrix.json b/reports/test-audit/flakiness-matrix.json index 5368ac2..828887a 100644 --- a/reports/test-audit/flakiness-matrix.json +++ b/reports/test-audit/flakiness-matrix.json @@ -1,618 +1,14 @@ { - "Build artifacts verification": { + "test/lock-ops-publish-retry.test.mjs": { + "pass": 5, + "fail": 0 + }, + "test/scheduler-lock-failure-schema.contract.test.mjs": { + "pass": 5, + "fail": 0 + }, + "test/scheduler-preflight-lock.e2e.test.mjs": { "pass": 0, "fail": 5 - }, - "parseArgs should parse basic command": { - "pass": 5, - "fail": 0 - }, - "parseArgs should parse agent and cadence": { - "pass": 5, - "fail": 0 - }, - "parseArgs should parse equals sign syntax": { - "pass": 5, - "fail": 0 - }, - "parseArgs should parse dry-run flag": { - "pass": 5, - "fail": 0 - }, - "parseArgs should parse port with default": { - "pass": 5, - "fail": 0 - }, - "parseArgs should parse custom port": { - "pass": 5, - "fail": 0 - }, - "parseArgs should parse ignore-logs flag": { - "pass": 5, - "fail": 0 - }, - "parseArgs should parse memory flags": { - "pass": 5, - "fail": 0 - }, - "parseArgs should parse memory flags with equals": { - "pass": 5, - "fail": 0 - }, - "cmdList": { - "pass": 5, - "fail": 0 - }, - "Dashboard Authentication": { - "pass": 5, - "fail": 0 - }, - "Dashboard Configuration Exposure Security": { - "pass": 5, - "fail": 0 - }, - "Dashboard Configuration Security": { - "pass": 5, - "fail": 0 - }, - "Dashboard Path Traversal Security Fix": { - "pass": 5, - "fail": 0 - }, - "Dashboard File Access Security": { - "pass": 5, - "fail": 0 - }, - "cmdInit should validate install directory name": { - "pass": 5, - "fail": 0 - }, - "src/lib.mjs": { - "pass": 5, - "fail": 0 - }, - "ci-resilience lock acquisition under unstable relays": { - "pass": 5, - "fail": 0 - }, - "publishLock retry behavior": { - "pass": 5, - "fail": 0 - }, - "lock-ops": { - "pass": 5, - "fail": 0 - }, - "lock-utils": { - "pass": 5, - "fail": 0 - }, - "listMemories, inspectMemory, pinMemory, and unpinMemory support admin hooks": { - "pass": 5, - "fail": 0 - }, - "triggerPruneDryRun returns candidate metadata without deleting records": { - "pass": 5, - "fail": 0 - }, - "memoryStats reports counts/rates and retrieval telemetry remains redaction-safe": { - "pass": 5, - "fail": 0 - }, - "runPruneCycle respects prune feature-flag modes": { - "pass": 5, - "fail": 0 - }, - "markMemoryMerged delegates to repository and clears cache": { - "pass": 5, - "fail": 0 - }, - "stores runtime events in signer/session namespace and clears scope": { - "pass": 5, - "fail": 0 - }, - "expires runtime events and records expiration metrics": { - "pass": 5, - "fail": 0 - }, - "enforces LRU bounds and tracks eviction metrics": { - "pass": 5, - "fail": 0 - }, - "blocks decrypted/session-sensitive data from durable promotion without sanitizer pass": { - "pass": 5, - "fail": 0 - }, - "createEmbedderAdapter selects in-memory backend and supports vector CRUD": { - "pass": 5, - "fail": 0 - }, - "createEmbedderAdapter reads backend from environment": { - "pass": 5, - "fail": 0 - }, - "memory feature flags parse defaults and canary allow-lists": { - "pass": 5, - "fail": 0 - }, - "memory prune flag supports off, dry-run, and active modes": { - "pass": 5, - "fail": 0 - }, - "estimateTokenCount returns heuristic count": { - "pass": 5, - "fail": 0 - }, - "formatMemoryBlock converts memory record to display block": { - "pass": 5, - "fail": 0 - }, - "formatMemoryBlock handles missing optional fields and default clamping": { - "pass": 5, - "fail": 0 - }, - "formatMemoryBlock respects custom clamping options": { - "pass": 5, - "fail": 0 - }, - "formatMemoryBlock handles edge cases for clamping": { - "pass": 5, - "fail": 0 - }, - "formatMemoriesForPrompt enforces token budget and k limit": { - "pass": 5, - "fail": 0 - }, - "formatMemoriesForPrompt handles aggressive budget fitting": { - "pass": 5, - "fail": 0 - }, - "renderPromptWithMemoryContext injects context when enabled": { - "pass": 5, - "fail": 0 - }, - "renderPromptWithMemoryContext returns base prompt when disabled": { - "pass": 5, - "fail": 0 - }, - "renderPromptWithMemoryContext handles missing query or service": { - "pass": 5, - "fail": 0 - }, - "renderPromptWithMemoryContext handles circular agentContext gracefully": { - "pass": 5, - "fail": 0 - }, - "renderPromptWithMemoryContext handles agentContext as array of strings": { - "pass": 5, - "fail": 0 - }, - "renderPromptWithMemoryContext handles agentContext as plain object": { - "pass": 5, - "fail": 0 - }, - "renderPromptWithMemoryContext handles agentContext as primitives": { - "pass": 5, - "fail": 0 - }, - "renderPromptWithMemoryContext calls updateMemoryUsage for retrieved memories": { - "pass": 5, - "fail": 0 - }, - "renderPromptWithMemoryContext handles empty memories gracefully": { - "pass": 5, - "fail": 0 - }, - "renderPromptWithMemoryContext handles null/undefined memories gracefully": { - "pass": 5, - "fail": 0 - }, - "renderPromptWithMemoryContext respects TORCH_MEMORY_ENABLED=false": { - "pass": 5, - "fail": 0 - }, - "renderPromptWithMemoryContext respects retrieval allowlist": { - "pass": 5, - "fail": 0 - }, - "renderPromptWithMemoryContext passes k and tokenBudget": { - "pass": 5, - "fail": 0 - }, - "getRelevantMemories uses cached results when available": { - "pass": 5, - "fail": 0 - }, - "getRelevantMemories calls ranker and updates cache on miss": { - "pass": 5, - "fail": 0 - }, - "getRelevantMemories passes correct parameters to ranker": { - "pass": 5, - "fail": 0 - }, - "getRelevantMemories triggers updateMemoryUsage on repository": { - "pass": 5, - "fail": 0 - }, - "ingestEvents": { - "pass": 5, - "fail": 0 - }, - "ingestMemoryWindow gathers sources, redacts pii, chunks, embeds, links, and emits telemetry": { - "pass": 5, - "fail": 0 - }, - "ingestMemoryWindow dedupes overlapping windows by hashed content + agent + timestamp bucket": { - "pass": 5, - "fail": 0 - }, - "pinMemory pins the memory and clears the cache": { - "pass": 5, - "fail": 0 - }, - "pinMemory returns null for non-existent ID but still clears cache": { - "pass": 5, - "fail": 0 - }, - "unpinMemory unpins the memory and clears the cache": { - "pass": 5, - "fail": 0 - }, - "unpinMemory returns null for non-existent ID but still clears cache": { - "pass": 5, - "fail": 0 - }, - "createLifecyclePlan respects delete safety constraints and logs reasons": { - "pass": 5, - "fail": 0 - }, - "createLifecyclePlan condenses near-duplicates and archives merged source records": { - "pass": 5, - "fail": 0 - }, - "applyLifecycleActions executes keep/archive/delete and merge markers": { - "pass": 5, - "fail": 0 - }, - "filterAndRankMemories applies metadata filters and composite score": { - "pass": 5, - "fail": 0 - }, - "formatMemoriesForPrompt returns prompt blocks and enforces token budget trimming": { - "pass": 5, - "fail": 0 - }, - "isMemoryRetrievalEnabled supports global toggle and agent canary list": { - "pass": 5, - "fail": 0 - }, - "renderPromptWithMemoryContext injects bounded memory block before base prompt": { - "pass": 5, - "fail": 0 - }, - "runPruneCycle respects off mode": { - "pass": 5, - "fail": 0 - }, - "runPruneCycle respects dry-run mode": { - "pass": 5, - "fail": 0 - }, - "runPruneCycle removes stale unpinned memories in active mode": { - "pass": 5, - "fail": 0 - }, - "runPruneCycle starts scheduler if requested": { - "pass": 5, - "fail": 0 - }, - "createDbAdvisoryLockProvider acquires and releases postgres advisory locks": { - "pass": 5, - "fail": 0 - }, - "startMemoryMaintenanceScheduler runs all jobs and emits metrics including retries": { - "pass": 5, - "fail": 0 - }, - "startMemoryMaintenanceScheduler skips jobs when feature flags disable memory": { - "pass": 5, - "fail": 0 - }, - "normalizeMemoryItem applies durable schema defaults and coercions": { - "pass": 5, - "fail": 0 - }, - "validateMemoryItem reports field-level errors": { - "pass": 5, - "fail": 0 - }, - "ingestEvents rejects invalid writes and logs structured validation details": { - "pass": 5, - "fail": 0 - }, - "memoryStats with empty repository returns zero counts and rates": { - "pass": 5, - "fail": 0 - }, - "memoryStats with populated repository returns correct counts and totals": { - "pass": 5, - "fail": 0 - }, - "memoryStats calculates ingest throughput based on window": { - "pass": 5, - "fail": 0 - }, - "memoryStats reflects deleted count from prune cycle": { - "pass": 5, - "fail": 0 - }, - "memoryStats handles windowMs parameter correctly": { - "pass": 5, - "fail": 0 - }, - "loadMemoryPromptTemplates loads all memory prompt templates from disk": { - "pass": 5, - "fail": 0 - }, - "summarizeEvents retries with repair prompt when first response is malformed JSON": { - "pass": 5, - "fail": 0 - }, - "summarizeEvents falls back to deterministic minimal summary with conservative importance when parsing fails twice": { - "pass": 5, - "fail": 0 - }, - "telemetry should not log to stdout or stderr by default": { - "pass": 5, - "fail": 0 - }, - "telemetry should log to stderr when NODE_DEBUG=torch-memory is set": { - "pass": 5, - "fail": 0 - }, - "schema validation covers required fields, coercion, and rejection paths": { - "pass": 5, - "fail": 0 - }, - "summarizer parser accepts valid json and recovers after malformed json response": { - "pass": 5, - "fail": 0 - }, - "ingest integration persists memory records and links embeddings from event batches": { - "pass": 5, - "fail": 0 - }, - "retrieval ranking blends semantic, importance, and recency signals": { - "pass": 5, - "fail": 0 - }, - "pruner policy protects pins, merges duplicates, and picks archive/delete outcomes": { - "pass": 5, - "fail": 0 - }, - "scheduler smoke test handles mocked clock and lock contention": { - "pass": 5, - "fail": 0 - }, - "CLI Smoke Test": { - "pass": 5, - "fail": 0 - }, - "cmdInit creates directory structure and files": { - "pass": 5, - "fail": 0 - }, - "cmdInit fails if directory exists without force": { - "pass": 5, - "fail": 0 - }, - "cmdUpdate preserves modified prompt": { - "pass": 5, - "fail": 0 - }, - "cmdUpdate overwrites prompt with force": { - "pass": 5, - "fail": 0 - }, - "cmdUpdate creates backup": { - "pass": 5, - "fail": 0 - }, - "torch-lock check respects local roster": { - "pass": 5, - "fail": 0 - }, - "cmdInit creates torch-config.json with random namespace": { - "pass": 5, - "fail": 0 - }, - "relay fanout quorum integration": { - "pass": 5, - "fail": 0 - }, - "RelayHealthManager": { - "pass": 5, - "fail": 0 - }, - "evaluateAlertThresholds": { - "pass": 5, - "fail": 0 - }, - "summarizeHistory - basic statistics calculation": { - "pass": 5, - "fail": 0 - }, - "summarizeHistory - handles empty history": { - "pass": 5, - "fail": 0 - }, - "summarizeHistory - respects time window for stats": { - "pass": 5, - "fail": 0 - }, - "summarizeHistory - allDownDurationMinutes considers outside window entries": { - "pass": 5, - "fail": 0 - }, - "summarizeHistory - allDownDurationMinutes is null if never healthy": { - "pass": 5, - "fail": 0 - }, - "summarizeHistory - allDownDurationMinutes is 0 if currently healthy": { - "pass": 5, - "fail": 0 - }, - "summarizeHistory - handles malformed entries gracefully": { - "pass": 5, - "fail": 0 - }, - "summarizeHistory - handles valid timestamp but missing summary": { - "pass": 5, - "fail": 0 - }, - "summarizeHistory - healthy entry with invalid timestamp results in NaN duration": { - "pass": 5, - "fail": 0 - }, - "Roster (Unit Tests with Dependency Injection)": { - "pass": 5, - "fail": 0 - }, - "fails required memory policy when retrieval/storage evidence is missing": { - "pass": 5, - "fail": 0 - }, - "accepts required memory policy when markers or artifacts are produced": { - "pass": 5, - "fail": 0 - }, - "records backend failure metadata when lock command exits with code 2": { - "pass": 5, - "fail": 0 - }, - "defers backend failures in non-strict mode and reuses idempotency key on successful retry": { - "pass": 5, - "fail": 0 - }, - "fails after exceeding non-strict deferral budget": { - "pass": 5, - "fail": 0 - }, - "strict lock mode fails immediately without deferral": { - "pass": 5, - "fail": 0 - }, - "skips lock health preflight by default when not enabled": { - "pass": 5, - "fail": 0 - }, - "fails scheduler with preflight metadata when lock health preflight is enabled": { - "pass": 5, - "fail": 0 - }, - "categorizes unreadable prompt files as prompt_parse_error": { - "pass": 5, - "fail": 0 - }, - "categorizes invalid prompt schema as prompt_schema_error": { - "pass": 5, - "fail": 0 - }, - "categorizes handoff runtime failures as execution_error": { - "pass": 5, - "fail": 0 - }, - "retries lock acquisition for backend error and succeeds on a later attempt": { - "pass": 5, - "fail": 0 - }, - "stops retrying after configured lock backend retries are exhausted": { - "pass": 5, - "fail": 0 - }, - "does not use backend retry flow for exit code 3 lock conflicts": { - "pass": 5, - "fail": 0 - }, - "runs selected scheduler prompt through configured runner command": { - "pass": 5, - "fail": 0 - }, - "propagates non-zero runner exit code": { - "pass": 5, - "fail": 0 - }, - "scheduler lock backend failure artifact matches required frontmatter schema": { - "pass": 5, - "fail": 0 - }, - "schema contract catches missing required lock-backend field": { - "pass": 5, - "fail": 0 - }, - "schema contract catches misnamed lock-backend key": { - "pass": 5, - "fail": 0 - }, - "lock preflight e2e: successful lock writes completed status snapshot": { - "pass": 5, - "fail": 0 - }, - "lock preflight e2e: exit code 2 quorum failure persists failed backend status and prompt-not-started marker": { - "pass": 5, - "fail": 0 - }, - "lock preflight e2e: non-lock failure exits failed without prompt parse/schema classification": { - "pass": 5, - "fail": 0 - }, - "Scheduler Ratchet Logic (Log Checking)": { - "pass": 5, - "fail": 0 - }, - "Scheduler cycle ordering guarantees": { - "pass": 5, - "fail": 0 - }, - "secureRandom": { - "pass": 5, - "fail": 0 - }, - "summarizes recent lock reliability by platform/cadence/backend/relay": { - "pass": 5, - "fail": 0 - }, - "loadTorchConfig (Unit Tests with Mocked FS)": { - "pass": 5, - "fail": 0 - }, - "torch-config": { - "pass": 5, - "fail": 0 - }, - "Date Utilities": { - "pass": 5, - "fail": 0 - }, - "Platform Utilities": { - "pass": 5, - "fail": 0 - }, - "File Utilities": { - "pass": 5, - "fail": 0 - }, - "passes when artifacts include metadata and failure IDs map to unresolved known issue entries": { - "pass": 5, - "fail": 0 - }, - "fails when required metadata is missing from artifacts": { - "pass": 5, - "fail": 0 - }, - "fails when failure identifiers are not cross-linked to known issues or incidents": { - "pass": 5, - "fail": 0 } } \ No newline at end of file diff --git a/reports/test-audit/flakiness-runs.json b/reports/test-audit/flakiness-runs.json new file mode 100644 index 0000000..3cde1b2 --- /dev/null +++ b/reports/test-audit/flakiness-runs.json @@ -0,0 +1,32 @@ +[ + { + "run": 1, + "code": 1, + "tapOutputPath": "reports/test-audit/flaky-run-01.tap", + "observed": 3 + }, + { + "run": 2, + "code": 1, + "tapOutputPath": "reports/test-audit/flaky-run-02.tap", + "observed": 3 + }, + { + "run": 3, + "code": 1, + "tapOutputPath": "reports/test-audit/flaky-run-03.tap", + "observed": 3 + }, + { + "run": 4, + "code": 1, + "tapOutputPath": "reports/test-audit/flaky-run-04.tap", + "observed": 3 + }, + { + "run": 5, + "code": 1, + "tapOutputPath": "reports/test-audit/flaky-run-05.tap", + "observed": 3 + } +] \ No newline at end of file diff --git a/reports/test-audit/flaky-run-01.tap b/reports/test-audit/flaky-run-01.tap new file mode 100644 index 0000000..06c390d --- /dev/null +++ b/reports/test-audit/flaky-run-01.tap @@ -0,0 +1,34 @@ +TAP version 13 +# Subtest: test/lock-ops-publish-retry.test.mjs +ok 1 - test/lock-ops-publish-retry.test.mjs + --- + duration_ms: 102.263713 + type: 'test' + ... +# Subtest: test/scheduler-lock-failure-schema.contract.test.mjs +ok 2 - test/scheduler-lock-failure-schema.contract.test.mjs + --- + duration_ms: 111.249474 + type: 'test' + ... +# Subtest: test/scheduler-preflight-lock.e2e.test.mjs +not ok 3 - test/scheduler-preflight-lock.e2e.test.mjs + --- + duration_ms: 286.217684 + type: 'test' + location: '/home/user/Documents/GitHub/TORCH/test/scheduler-preflight-lock.e2e.test.mjs:1:1' + failureType: 'testCodeFailure' + exitCode: 1 + signal: ~ + error: 'test failed' + code: 'ERR_TEST_FAILURE' + ... +1..3 +# tests 3 +# suites 0 +# pass 2 +# fail 1 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms 300.601253 diff --git a/reports/test-audit/flaky-run-02.tap b/reports/test-audit/flaky-run-02.tap new file mode 100644 index 0000000..38c5c10 --- /dev/null +++ b/reports/test-audit/flaky-run-02.tap @@ -0,0 +1,34 @@ +TAP version 13 +# Subtest: test/lock-ops-publish-retry.test.mjs +ok 1 - test/lock-ops-publish-retry.test.mjs + --- + duration_ms: 136.571153 + type: 'test' + ... +# Subtest: test/scheduler-lock-failure-schema.contract.test.mjs +ok 2 - test/scheduler-lock-failure-schema.contract.test.mjs + --- + duration_ms: 150.466335 + type: 'test' + ... +# Subtest: test/scheduler-preflight-lock.e2e.test.mjs +not ok 3 - test/scheduler-preflight-lock.e2e.test.mjs + --- + duration_ms: 315.710253 + type: 'test' + location: '/home/user/Documents/GitHub/TORCH/test/scheduler-preflight-lock.e2e.test.mjs:1:1' + failureType: 'testCodeFailure' + exitCode: 1 + signal: ~ + error: 'test failed' + code: 'ERR_TEST_FAILURE' + ... +1..3 +# tests 3 +# suites 0 +# pass 2 +# fail 1 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms 333.778214 diff --git a/reports/test-audit/flaky-run-03.tap b/reports/test-audit/flaky-run-03.tap new file mode 100644 index 0000000..698c285 --- /dev/null +++ b/reports/test-audit/flaky-run-03.tap @@ -0,0 +1,34 @@ +TAP version 13 +# Subtest: test/lock-ops-publish-retry.test.mjs +ok 1 - test/lock-ops-publish-retry.test.mjs + --- + duration_ms: 134.112659 + type: 'test' + ... +# Subtest: test/scheduler-lock-failure-schema.contract.test.mjs +ok 2 - test/scheduler-lock-failure-schema.contract.test.mjs + --- + duration_ms: 135.921496 + type: 'test' + ... +# Subtest: test/scheduler-preflight-lock.e2e.test.mjs +not ok 3 - test/scheduler-preflight-lock.e2e.test.mjs + --- + duration_ms: 316.604483 + type: 'test' + location: '/home/user/Documents/GitHub/TORCH/test/scheduler-preflight-lock.e2e.test.mjs:1:1' + failureType: 'testCodeFailure' + exitCode: 1 + signal: ~ + error: 'test failed' + code: 'ERR_TEST_FAILURE' + ... +1..3 +# tests 3 +# suites 0 +# pass 2 +# fail 1 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms 334.498823 diff --git a/reports/test-audit/flaky-run-04.tap b/reports/test-audit/flaky-run-04.tap new file mode 100644 index 0000000..20d36d6 --- /dev/null +++ b/reports/test-audit/flaky-run-04.tap @@ -0,0 +1,34 @@ +TAP version 13 +# Subtest: test/lock-ops-publish-retry.test.mjs +ok 1 - test/lock-ops-publish-retry.test.mjs + --- + duration_ms: 125.402872 + type: 'test' + ... +# Subtest: test/scheduler-lock-failure-schema.contract.test.mjs +ok 2 - test/scheduler-lock-failure-schema.contract.test.mjs + --- + duration_ms: 132.678397 + type: 'test' + ... +# Subtest: test/scheduler-preflight-lock.e2e.test.mjs +not ok 3 - test/scheduler-preflight-lock.e2e.test.mjs + --- + duration_ms: 442.786008 + type: 'test' + location: '/home/user/Documents/GitHub/TORCH/test/scheduler-preflight-lock.e2e.test.mjs:1:1' + failureType: 'testCodeFailure' + exitCode: 1 + signal: ~ + error: 'test failed' + code: 'ERR_TEST_FAILURE' + ... +1..3 +# tests 3 +# suites 0 +# pass 2 +# fail 1 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms 460.996364 diff --git a/reports/test-audit/flaky-run-05.tap b/reports/test-audit/flaky-run-05.tap new file mode 100644 index 0000000..51077d3 --- /dev/null +++ b/reports/test-audit/flaky-run-05.tap @@ -0,0 +1,34 @@ +TAP version 13 +# Subtest: test/lock-ops-publish-retry.test.mjs +ok 1 - test/lock-ops-publish-retry.test.mjs + --- + duration_ms: 118.053138 + type: 'test' + ... +# Subtest: test/scheduler-lock-failure-schema.contract.test.mjs +ok 2 - test/scheduler-lock-failure-schema.contract.test.mjs + --- + duration_ms: 127.486819 + type: 'test' + ... +# Subtest: test/scheduler-preflight-lock.e2e.test.mjs +not ok 3 - test/scheduler-preflight-lock.e2e.test.mjs + --- + duration_ms: 340.626807 + type: 'test' + location: '/home/user/Documents/GitHub/TORCH/test/scheduler-preflight-lock.e2e.test.mjs:1:1' + failureType: 'testCodeFailure' + exitCode: 1 + signal: ~ + error: 'test failed' + code: 'ERR_TEST_FAILURE' + ... +1..3 +# tests 3 +# suites 0 +# pass 2 +# fail 1 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms 357.903984 diff --git a/reports/test-audit/scheduler-preflight-direct.exitcode b/reports/test-audit/scheduler-preflight-direct.exitcode new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/reports/test-audit/scheduler-preflight-direct.exitcode @@ -0,0 +1 @@ +1 diff --git a/reports/test-audit/scheduler-preflight.exitcode b/reports/test-audit/scheduler-preflight.exitcode new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/reports/test-audit/scheduler-preflight.exitcode @@ -0,0 +1 @@ +1 diff --git a/reports/test-audit/suspicious-tests.json b/reports/test-audit/suspicious-tests.json index 0294568..1cf3e8a 100644 --- a/reports/test-audit/suspicious-tests.json +++ b/reports/test-audit/suspicious-tests.json @@ -5,22 +5,16 @@ "No obvious assertions" ] }, - { - "file": "test/memory-cache.test.mjs", - "issues": [ - "Found setTimeout()" - ] - }, - { - "file": "test/memory.test.js", - "issues": [ - "Found setTimeout()" - ] - }, { "file": "test/relay-fanout-quorum.integration.test.mjs", "issues": [ "Found setTimeout()" ] + }, + { + "file": "test/utils-async.test.mjs", + "issues": [ + "Found setTimeout()" + ] } ] \ No newline at end of file diff --git a/reports/test-audit/test-audit-report-2026-02-21.md b/reports/test-audit/test-audit-report-2026-02-21.md new file mode 100644 index 0000000..f047c1b --- /dev/null +++ b/reports/test-audit/test-audit-report-2026-02-21.md @@ -0,0 +1,89 @@ +# Test Audit Report - 2026-02-21 + +## Scope +- Agent: `test-audit-agent` +- Cadence: `daily` +- Prompt: `src/prompts/daily/test-audit-agent.md` +- Focus: scenario fidelity, determinism, and cheat-vector resistance in audit tooling/tests. + +## Discovery +- Test runner entry points: + - `npm test` -> `npm run validate:scheduler && npm run test:integration:e2e && node --test --test-timeout=30000 test/*.test.mjs test/*.test.js` + - `npm run test:integration:e2e` -> `node --test test/scheduler-preflight-lock.e2e.test.mjs` +- CI entry points: + - `.github/workflows/ci.yml` runs `npm run lint` and `npm test`. +- Audit tools used: + - `scripts/test-audit/run-static-analysis.mjs` + - `scripts/test-audit/run-flaky-check.mjs` + +## Findings +1. **High - flakiness signal could be silently false-green** + - `scripts/test-audit/run-flaky-check.mjs` parsed only piped child stdout and produced `{}` in this environment, even when tests emitted pass/fail TAP lines. + - Risk: audit report can claim "no flakes" with zero observed outcomes. +2. **Medium - unresolved scheduler e2e platform mismatch** + - `test/scheduler-preflight-lock.e2e.test.mjs` still fails on `platform` expectation (`unknown` expected vs `codex` actual). + - Treated as unresolved spec/contract mismatch; test expectation was not weakened. +3. **Low - static-analysis noise** + - `reports/test-audit/suspicious-tests.json` flags `test/fixtures/memory-fixtures.js` for assertions (fixture-only file), plus intentional `setTimeout` usages in timing-related tests. + +## Changes Applied +- Hardened `scripts/test-audit/run-flaky-check.mjs` to: + - force TAP reporter output to per-run files (`flaky-run-*.tap`), + - parse TAP files deterministically, + - emit run diagnostics (`flakiness-runs.json`), + - fail closed when zero outcomes are observed. +- Added integration regression coverage: + - `test/test-audit-run-flaky-check.test.mjs` + - fixtures: `test/fixtures/flaky-check/pass.fixture.test.mjs`, `test/fixtures/flaky-check/fail.fixture.test.mjs` + +## Scenario List (Given/When/Then) +- `SCN-flaky-check-captures-results` + - Given deterministic pass and fail fixture tests, + - When `run-flaky-check.mjs` executes 5 runs, + - Then `flakiness-matrix.json` records 5 pass counts for the pass fixture and 5 fail counts for the fail fixture. +- `SCN-flaky-check-fail-closed-on-no-signal` + - Given no observed TAP outcomes across all runs, + - When `run-flaky-check.mjs` completes, + - Then the command exits non-zero to prevent false-green flakiness reports. + +## Validation +- `node scripts/test-audit/run-static-analysis.mjs --output-dir reports/test-audit` +- `node scripts/test-audit/run-flaky-check.mjs --output-dir reports/test-audit test/scheduler-preflight-lock.e2e.test.mjs test/scheduler-lock-failure-schema.contract.test.mjs test/lock-ops-publish-retry.test.mjs` +- `node --test test/test-audit-run-flaky-check.test.mjs` +- `node test/scheduler-preflight-lock.e2e.test.mjs` (repro unresolved issue) + +## Needs Spec Clarification +- Scheduler e2e platform assertion source of truth is unclear in this environment. +- Candidate scenarios: + - Given scheduler completion log frontmatter, when runtime platform is detected/declared, then platform value should come from one canonical source (`AGENT_PLATFORM` or detector), consistently in code and tests. + +## Test Integrity Note +```yaml +test_integrity_note: + change_type: ["new_tests", "refactor_tests", "flake_fix"] + scenarios: + - id: SCN-flaky-check-captures-results + given: "A deterministic pass fixture and deterministic fail fixture" + when: "run-flaky-check executes five node:test runs" + then: "flakiness matrix records pass/fail counts per fixture across all runs" + - id: SCN-flaky-check-fail-closed-on-no-signal + given: "No TAP outcomes are captured for all runs" + when: "run-flaky-check finishes aggregation" + then: "script exits non-zero instead of emitting a false-green empty matrix" + observable_outcomes: + - "reports/test-audit/flakiness-matrix.json includes concrete pass/fail counters" + - "reports/test-audit/flakiness-runs.json includes per-run observed counts and exit codes" + - "process exit code is non-zero when observed outcome count is zero" + determinism_controls: + - "Fixture tests are deterministic and local" + - "TAP reporter output is written to explicit files instead of environment-sensitive piped stdio" + anti_cheat_rationale: + prevents: + - "hard-coded empty flakiness output" + - "false-green flake audit when no outcomes are parsed" + - "environment-dependent stdout capture masking failures" + - "retry/sleep-based flake masking" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` diff --git a/reports/test-audit/test-audit-run-flaky-check.exitcode b/reports/test-audit/test-audit-run-flaky-check.exitcode new file mode 100644 index 0000000..573541a --- /dev/null +++ b/reports/test-audit/test-audit-run-flaky-check.exitcode @@ -0,0 +1 @@ +0 diff --git a/scripts/agent/run-scheduler-cycle.mjs b/scripts/agent/run-scheduler-cycle.mjs index 5c7f088..7dcdf3b 100644 --- a/scripts/agent/run-scheduler-cycle.mjs +++ b/scripts/agent/run-scheduler-cycle.mjs @@ -47,6 +47,7 @@ import { randomUUID } from 'node:crypto'; import { runCommand, parseJsonFromOutput, + parseJsonEventsFromOutput, readJson, normalizeStringList, parseNonNegativeInt, @@ -61,6 +62,7 @@ import { parseTimestampFromFilename, isStrictSchedulerLogFilename, parseAgentFromFilename, + sleep, } from './scheduler-utils.mjs'; import { @@ -82,6 +84,12 @@ const FAILURE_CATEGORY = { EXECUTION: 'execution_error', }; +const HANDOFF_FAILURE = { + NETWORK: 'network_error', + RUNNER_UNAVAILABLE: 'runner_unavailable', + UNKNOWN: 'unknown_error', +}; + /** * Parses CLI arguments into a structured options object. * Supports both positional cadence (`daily`|`weekly`) and named flags. @@ -141,6 +149,23 @@ function categorizeFailureMetadata(failureCategory, extraMetadata = {}) { }; } +function getDefaultRunnerHealthCommand({ platform, handoffCommand }) { + if (!handoffCommand || !handoffCommand.includes('run-selected-prompt.mjs')) { + return null; + } + const normalizedPlatform = String(platform || '').trim().toLowerCase(); + if (normalizedPlatform === 'codex') { + return 'command -v codex >/dev/null 2>&1'; + } + if (normalizedPlatform === 'claude') { + return 'command -v claude >/dev/null 2>&1'; + } + if (normalizedPlatform === 'linux') { + return 'command -v echo >/dev/null 2>&1'; + } + return null; +} + /** * Validates that a prompt file is readable and contains a valid markdown heading * or blockquote as its first non-empty line. @@ -225,6 +250,55 @@ async function verifyMemoryStep({ name, markers, artifacts, outputText, sinceMs }; } +function classifyHandoffFailure(text) { + const normalized = String(text || '').toLowerCase(); + if (!normalized) return HANDOFF_FAILURE.UNKNOWN; + + if ( + normalized.includes('stream disconnected') + || normalized.includes('econnreset') + || normalized.includes('enotfound') + || normalized.includes('eai_again') + || normalized.includes('timed out') + || normalized.includes('timeout') + || normalized.includes('temporarily unavailable') + || normalized.includes('connection reset') + || normalized.includes('502') + || normalized.includes('503') + || normalized.includes('504') + ) { + return HANDOFF_FAILURE.NETWORK; + } + + if ( + normalized.includes('runner command not found') + || normalized.includes('command not found') + || normalized.includes('enoent') + || normalized.includes('unsupported agent_platform') + ) { + return HANDOFF_FAILURE.RUNNER_UNAVAILABLE; + } + + return HANDOFF_FAILURE.UNKNOWN; +} + +function isRetryableHandoffFailure({ failureCategory, combinedOutput, retryOnPatterns }) { + if (failureCategory === HANDOFF_FAILURE.NETWORK) { + return true; + } + const text = String(combinedOutput || ''); + for (const pattern of retryOnPatterns || []) { + try { + if (new RegExp(pattern, 'i').test(text)) { + return true; + } + } catch { + // Invalid regex in config; ignore and continue. + } + } + return false; +} + /** * Finds the most recent valid scheduler task log file in `logDir`. @@ -333,6 +407,7 @@ async function getSchedulerConfig(cadence, { isInteractive }) { const handoffCommand = scheduler.handoffCommandByCadence?.[cadence] || null; const missingHandoffCommandForMode = !isInteractive && !handoffCommand; const memoryPolicyRaw = scheduler.memoryPolicyByCadence?.[cadence] || {}; + const handoffPolicyRaw = scheduler.handoffPolicyByCadence?.[cadence] || {}; const mode = memoryPolicyRaw.mode === 'required' ? 'required' : 'optional'; const lockRetryRaw = scheduler.lockRetry || {}; const maxRetries = parseNonNegativeInt( @@ -365,11 +440,42 @@ async function getSchedulerConfig(cadence, { isInteractive }) { process.env.SCHEDULER_MAX_DEFERRALS, parseNonNegativeInt(scheduler.max_deferrals, 3), ); + const handoffMaxAttempts = Math.max(1, parseNonNegativeInt( + process.env.SCHEDULER_HANDOFF_MAX_ATTEMPTS, + parseNonNegativeInt(handoffPolicyRaw.maxAttempts, 1), + )); + const handoffRetryBackoffMs = parseNonNegativeInt( + process.env.SCHEDULER_HANDOFF_RETRY_BACKOFF_MS, + parseNonNegativeInt(handoffPolicyRaw.retryBackoffMs, 1000), + ); + const runnerHealthCommand = (process.env.SCHEDULER_RUNNER_HEALTH_COMMAND + || scheduler.runnerHealthCommandByCadence?.[cadence] + || null); + const fallbackPlatform = (process.env.SCHEDULER_HANDOFF_FALLBACK_PLATFORM + || handoffPolicyRaw.fallbackPlatform + || null); + const retryOnPatterns = normalizeStringList(handoffPolicyRaw.retryOnPatterns, [ + 'stream disconnected', + 'timeout', + 'temporarily unavailable', + 'connection reset', + ]); return { firstPrompt: scheduler.firstPromptByCadence?.[cadence] || null, handoffCommand, + runnerHealthCommand: typeof runnerHealthCommand === 'string' && runnerHealthCommand.trim() + ? runnerHealthCommand.trim() + : null, missingHandoffCommandForMode, + handoffPolicy: { + maxAttempts: handoffMaxAttempts, + retryBackoffMs: handoffRetryBackoffMs, + retryOnPatterns, + fallbackPlatform: typeof fallbackPlatform === 'string' && fallbackPlatform.trim() + ? fallbackPlatform.trim() + : null, + }, validationCommands: Array.isArray(scheduler.validationCommandsByCadence?.[cadence]) ? scheduler.validationCommandsByCadence[cadence].filter((cmd) => typeof cmd === 'string' && cmd.trim()) : ['npm run lint'], @@ -582,6 +688,8 @@ async function main() { const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY); const schedulerConfig = await getSchedulerConfig(cadence, { isInteractive }); + const runnerHealthPreflightCommand = schedulerConfig.runnerHealthCommand + || getDefaultRunnerHealthCommand({ platform, handoffCommand: schedulerConfig.handoffCommand }); const logDir = path.resolve(process.cwd(), 'task-logs', cadence); const agentsPath = path.resolve(process.cwd(), 'AGENTS.md'); @@ -636,7 +744,14 @@ async function main() { // Step 6: Build exclusion set from relay state. Prefer the top-level `excluded` // field (computed by cmdCheck), falling back to the union of locked/paused/completed. const checkResult = await runCommand('npm', ['run', `lock:check:${cadence}`, '--', '--json', '--quiet']); - const checkPayload = parseJsonFromOutput(`${checkResult.stdout}\n${checkResult.stderr}`) || {}; + const checkCombinedOutput = `${checkResult.stdout}\n${checkResult.stderr}`; + const checkEvents = parseJsonEventsFromOutput(checkCombinedOutput); + const checkPayload = checkEvents.findLast((event) => ( + Array.isArray(event.excluded) + || Array.isArray(event.locked) + || Array.isArray(event.paused) + || Array.isArray(event.completed) + )) || parseJsonFromOutput(checkCombinedOutput) || {}; const excluded = Array.isArray(checkPayload.excluded) ? checkPayload.excluded : [...new Set([ @@ -711,6 +826,43 @@ async function main() { }); } + if (runnerHealthPreflightCommand) { + const runnerPreflightPromptPath = path.resolve(process.cwd(), 'src/prompts', cadence, `${selectedAgent}.md`); + const runnerPreflight = await runCommand('bash', ['-lc', runnerHealthPreflightCommand], { + env: { + AGENT_PLATFORM: platform, + ...(model ? { AGENT_MODEL: model } : {}), + SCHEDULER_AGENT: selectedAgent, + SCHEDULER_CADENCE: cadence, + SCHEDULER_PROMPT_PATH: runnerPreflightPromptPath, + }, + }); + if (runnerPreflight.code !== 0) { + await writeLog({ + cadence, + agent: selectedAgent, + status: 'failed', + platform, + reason: 'Runner health preflight failed', + detail: `Prompt not executed. Command failed: ${runnerHealthPreflightCommand}`, + metadata: categorizeFailureMetadata(FAILURE_CATEGORY.EXECUTION, { + failure_class: 'runner_unavailable', + runner_health_command: runnerHealthPreflightCommand, + runner_health_stdout_excerpt: excerptText(runnerPreflight.stdout) || '(empty)', + runner_health_stderr_excerpt: excerptText(runnerPreflight.stderr) || '(empty)', + }), + }); + await exitWithSummary(runnerPreflight.code, { + status: 'failed', + agent: selectedAgent, + promptPath: runnerPreflightPromptPath, + reason: 'Runner health preflight failed', + detail: runnerHealthPreflightCommand, + platform, + }); + } + } + const deferralForAgent = schedulerRunState.lock_deferral?.selected_agent === selectedAgent ? schedulerRunState.lock_deferral : null; @@ -910,26 +1062,95 @@ async function main() { } if (schedulerConfig.handoffCommand) { - const handoff = await runCommand('bash', ['-lc', schedulerConfig.handoffCommand], { - env: schedulerEnv, - }); - outputChunks.push(handoff.stdout, handoff.stderr); - if (handoff.code !== 0) { + const handoffAttempts = []; + const maxAttempts = schedulerConfig.handoffPolicy.maxAttempts; + let handoffSucceeded = false; + let fallbackUsed = false; + let attempt = 0; + let finalHandoff = { code: 1, stdout: '', stderr: '' }; + + while (attempt < maxAttempts) { + attempt += 1; + const handoff = await runCommand('bash', ['-lc', schedulerConfig.handoffCommand], { + env: schedulerEnv, + }); + handoffAttempts.push(handoff); + outputChunks.push(handoff.stdout, handoff.stderr); + finalHandoff = handoff; + if (handoff.code === 0) { + handoffSucceeded = true; + break; + } + const combinedOutput = `${handoff.stdout}\n${handoff.stderr}`; + const failureCategory = classifyHandoffFailure(combinedOutput); + const retryable = isRetryableHandoffFailure({ + failureCategory, + combinedOutput, + retryOnPatterns: schedulerConfig.handoffPolicy.retryOnPatterns, + }); + if (!retryable || attempt >= maxAttempts) { + break; + } + if (schedulerConfig.handoffPolicy.retryBackoffMs > 0) { + await sleep(schedulerConfig.handoffPolicy.retryBackoffMs * attempt); + } + } + + if ( + !handoffSucceeded + && schedulerConfig.handoffPolicy.fallbackPlatform + && schedulerConfig.handoffPolicy.fallbackPlatform !== platform + ) { + const fallbackPlatform = schedulerConfig.handoffPolicy.fallbackPlatform; + const fallbackResult = await runCommand('bash', ['-lc', schedulerConfig.handoffCommand], { + env: { + ...schedulerEnv, + AGENT_PLATFORM: fallbackPlatform, + }, + }); + handoffAttempts.push(fallbackResult); + outputChunks.push(fallbackResult.stdout, fallbackResult.stderr); + finalHandoff = fallbackResult; + fallbackUsed = true; + if (fallbackResult.code === 0) { + handoffSucceeded = true; + } + } + + if (!handoffSucceeded) { + const combinedOutput = `${finalHandoff.stdout}\n${finalHandoff.stderr}`; + const failureCategory = classifyHandoffFailure(combinedOutput); + const retryable = isRetryableHandoffFailure({ + failureCategory, + combinedOutput, + retryOnPatterns: schedulerConfig.handoffPolicy.retryOnPatterns, + }); + const detail = `Handoff callback failed after ${handoffAttempts.length} attempt(s)${fallbackUsed ? ` including fallback platform ${schedulerConfig.handoffPolicy.fallbackPlatform}` : ''}.`; await writeLog({ cadence, agent: selectedAgent, status: 'failed', platform, reason: 'Prompt/handoff execution failed', - detail: 'Handoff callback failed.', - metadata: categorizeFailureMetadata(FAILURE_CATEGORY.EXECUTION, { failure_class: 'prompt_validation_error' }), + detail, + metadata: categorizeFailureMetadata(FAILURE_CATEGORY.EXECUTION, { + failure_class: 'prompt_validation_error', + handoff_failure_category: failureCategory, + handoff_retryable: retryable, + handoff_attempts: handoffAttempts.length, + handoff_fallback_used: fallbackUsed, + handoff_fallback_platform: fallbackUsed ? schedulerConfig.handoffPolicy.fallbackPlatform : null, + handoff_last_exit_code: finalHandoff.code, + handoff_stdout_excerpt: excerptText(finalHandoff.stdout) || '(empty)', + handoff_stderr_excerpt: excerptText(finalHandoff.stderr) || '(empty)', + }), }); - await exitWithSummary(handoff.code, { + await exitWithSummary(finalHandoff.code || 1, { status: 'failed', agent: selectedAgent, promptPath, reason: 'Prompt/handoff execution failed', - detail: 'Handoff callback failed', + detail, memoryFile, platform, }); diff --git a/scripts/memory/retrieve.mjs b/scripts/memory/retrieve.mjs index 4bdda00..4cf1a75 100644 --- a/scripts/memory/retrieve.mjs +++ b/scripts/memory/retrieve.mjs @@ -84,6 +84,7 @@ try { writeFileSync(path.join(sessionDir, 'retrieve.json'), JSON.stringify(artifact, null, 2)); writeFileSync(path.join(sessionDir, 'retrieve.ok'), 'MEMORY_RETRIEVED\n'); writeFileSync(path.join(latestDir, 'retrieve.ok'), 'MEMORY_RETRIEVED\n'); + writeFileSync(path.join(latestDir, 'retrieve.json'), JSON.stringify(artifact, null, 2)); // Format retrieved memories as readable markdown sections. // Prefer m.summary (compact excerpt) over m.content (full blob) for prompt injection. const memoriesMarkdown = retrieved.length > 0 diff --git a/scripts/memory/store.mjs b/scripts/memory/store.mjs index cf0e170..e365e46 100644 --- a/scripts/memory/store.mjs +++ b/scripts/memory/store.mjs @@ -108,6 +108,7 @@ try { writeFileSync(path.join(sessionDir, 'store.json'), JSON.stringify(artifact, null, 2)); writeFileSync(path.join(sessionDir, 'store.ok'), 'MEMORY_STORED\n'); writeFileSync(path.join(latestDir, 'store.ok'), 'MEMORY_STORED\n'); + writeFileSync(path.join(latestDir, 'store.json'), JSON.stringify(artifact, null, 2)); // 4. Output success marker to stdout (for scheduler verification) console.log('MEMORY_STORED'); diff --git a/scripts/test-audit/run-flaky-check.mjs b/scripts/test-audit/run-flaky-check.mjs index 575e518..e693d65 100644 --- a/scripts/test-audit/run-flaky-check.mjs +++ b/scripts/test-audit/run-flaky-check.mjs @@ -1,9 +1,10 @@ import { spawn } from 'node:child_process'; -import { writeFileSync, mkdirSync } from 'node:fs'; +import { writeFileSync, mkdirSync, readFileSync, existsSync } from 'node:fs'; import { join } from 'node:path'; const RUNS = 5; const results = {}; +const runDiagnostics = []; // Parse arguments to separate our flags from test runner args const args = process.argv.slice(2); @@ -22,44 +23,65 @@ for (let i = 0; i < args.length; i++) { async function runTests(i) { console.log(`Run ${i + 1}/${RUNS}...`); return new Promise((resolve) => { - // Pass strictly the test args to the child process - const child = spawn('node', ['--test', ...testArgs], { - stdio: ['ignore', 'pipe', 'pipe'] + const tapOutputPath = join(outputDir, `flaky-run-${String(i + 1).padStart(2, '0')}.tap`); + const child = spawn(process.execPath, [ + '--test', + '--test-reporter=tap', + `--test-reporter-destination=${tapOutputPath}`, + ...testArgs + ], { + stdio: ['ignore', 'ignore', 'ignore'] }); - let stdout = ''; - - child.stdout.on('data', (d) => stdout += d.toString()); - - child.on('close', (_code) => { - const lines = stdout.split('\n'); + child.on('close', (code) => { + let observed = 0; + const tapOutput = existsSync(tapOutputPath) ? readFileSync(tapOutputPath, 'utf8') : ''; + const lines = tapOutput.split('\n'); for (const line of lines) { if (line.startsWith('not ok ') && !line.includes('# skip') && !line.includes('# todo')) { const name = line.substring(line.indexOf('-') + 1).trim(); if (!results[name]) results[name] = { pass: 0, fail: 0 }; results[name].fail++; + observed++; } else if (line.startsWith('ok ') && !line.includes('# skip') && !line.includes('# todo')) { const name = line.substring(line.indexOf('-') + 1).trim(); if (!results[name]) results[name] = { pass: 0, fail: 0 }; results[name].pass++; + observed++; } } + runDiagnostics.push({ + run: i + 1, + code: code ?? 1, + tapOutputPath, + observed + }); resolve(); }); }); } async function main() { + // Ensure output directory exists before creating per-run TAP files + mkdirSync(outputDir, { recursive: true }); + for (let i = 0; i < RUNS; i++) { await runTests(i); } - // Ensure output directory exists - mkdirSync(outputDir, { recursive: true }); - const outputPath = join(outputDir, 'flakiness-matrix.json'); writeFileSync(outputPath, JSON.stringify(results, null, 2)); console.log(`Flakiness matrix written to ${outputPath}.`); + + const diagnosticsPath = join(outputDir, 'flakiness-runs.json'); + writeFileSync(diagnosticsPath, JSON.stringify(runDiagnostics, null, 2)); + + // Fail closed: an empty matrix means the signal is missing, not "no flakiness". + const totalObserved = runDiagnostics.reduce((sum, run) => sum + run.observed, 0); + if (totalObserved === 0) { + console.error('No TAP test outcomes were observed across runs; flakiness report is not trustworthy.'); + process.exitCode = 1; + } } main(); diff --git a/src/backlog/BACKLOG.md b/src/backlog/BACKLOG.md new file mode 100644 index 0000000..23b9dc9 --- /dev/null +++ b/src/backlog/BACKLOG.md @@ -0,0 +1,143 @@ +# Implementation Backlog — 2026-02-21 + +## Triage Summary +- Items evaluated: 18 +- New items added: 7 +- Items retired: 2 +- Items promoted to ready: 1 + +## Priority 1: Ready to Plan + +### BACKLOG-001: Add `npm run format` script to package.json +- **Source:** `KNOWN_ISSUES.md`, `src/todo/TODO_2026-02-18.md` +- **Impact:** Unblocks formatting workflows and prevents repeated `style-agent` no-op/failure risk. +- **Scope:** Add `format` and `format:check` scripts in `package.json`; verify they run in this repo. +- **Status:** ready-to-plan +- **Acceptance:** `npm run format -- --check` and `npm run format:check` execute successfully. + +### BACKLOG-002: Create `src/lib/eventSchemas.js` canonical event builders +- **Source:** weekly `event-schema-agent` failures; missing `src/lib/eventSchemas.js` +- **Impact:** Unblocks schema-related weekly work and removes repeated missing-file dead end. +- **Scope:** Extract/centralize lock-event schema builders into `src/lib/eventSchemas.js` and wire current call sites. +- **Status:** ready-to-plan +- **Acceptance:** `src/lib/eventSchemas.js` exists, is imported by lock/event code paths, and related tests pass. + +### BACKLOG-003: Revise `content-audit-agent` scope to TORCH-relevant surfaces +- **Source:** `docs/agent-handoffs/incidents/2026-02-20-content-audit-no-upload-surface.md` +- **Impact:** Replaces a recurring no-op daily run with actionable audits. +- **Scope:** Update the prompt to target real TORCH docs/contracts and expected outputs. +- **Status:** ready-to-plan +- **Acceptance:** Next `content-audit-agent` run produces findings against existing TORCH files/features. + +### BACKLOG-004: Implement prompt graph metadata extraction (Phase 0) +- **Source:** `docs/proposals/node-based-prompt-editor.md` +- **Impact:** Enables prompt fleet visibility, dependency mapping, and higher-quality governance analysis. +- **Scope:** Add extraction/build/validation scripts and emit graph artifact under `artifacts/prompt-graph/`. +- **Status:** ready-to-plan +- **Acceptance:** `npm run graph:build` (or equivalent script) generates a valid prompt graph including all roster agents. + +### BACKLOG-012: Align scheduler preflight platform assertion with runtime contract +- **Source:** `KNOWN_ISSUES.md` (`KNOWN-ISSUE-scheduler-preflight-platform`), `reports/test-audit/test-audit-report-2026-02-21.md` +- **Impact:** Removes a deterministic false failure in scheduler E2E validation. +- **Scope:** Define a single platform source-of-truth and update scheduler/test expectations accordingly via spec-correction protocol. +- **Status:** ready-to-plan +- **Acceptance:** `node --test test/scheduler-preflight-lock.e2e.test.mjs` passes without weakening scenario assertions. + +## Priority 2: Needs Scoping + +### BACKLOG-005: Implement `e2e-agent` for scenario-first E2E test maintenance +- **Source:** `docs/AGENT_COVERAGE_GAPS.md` +- **Impact:** Adds explicit ownership for scenario-level regressions and anti-cheat integrity. +- **Scope:** New weekly agent plus deterministic validation workflow. +- **Status:** needs-scoping + +### BACKLOG-006: Implement `memory-health-agent` for memory subsystem auditing +- **Source:** `docs/AGENT_COVERAGE_GAPS.md` +- **Impact:** Adds quality controls for retrieval relevance and memory lifecycle behavior. +- **Scope:** New weekly agent with measurable health criteria. +- **Status:** needs-scoping + +### BACKLOG-007: Implement `relay-health-agent` for relay stability operations +- **Source:** `docs/AGENT_COVERAGE_GAPS.md`, relay incidents in `KNOWN_ISSUES.md` +- **Impact:** Improves lock reliability and actionable relay operations beyond current preflight checks. +- **Scope:** Define monitor/rotation policy and integration boundaries with existing `lock:health` flows. +- **Status:** needs-scoping + +### BACKLOG-008: Implement project-type prompt packages system +- **Source:** `docs/proposals/prompt-packages.md` +- **Impact:** Improves onboarding and relevance outside TORCH’s own repo shape. +- **Scope:** `torch init` package selection, metadata schema, and package-aware prompt installation. +- **Status:** needs-scoping + +### BACKLOG-013: Stabilize `test/memory-telemetry.test.mjs` child-process signal capture +- **Source:** `KNOWN_ISSUES.md` (`KNOWN-ISSUE-memory-telemetry-stdout-stderr`) +- **Impact:** Removes recurring false negatives in memory telemetry test coverage. +- **Scope:** Investigate environment-sensitive stdio capture and replace with deterministic boundary assertions. +- **Status:** needs-scoping + +### BACKLOG-014: Enforce artifact metadata requirements at prompt/template level +- **Source:** `docs/agent-handoffs/incidents/2026-02-20-pr-review-agent-artifact-metadata-failure.md`, `task-logs/daily/2026-02-20T04-25-38Z__load-test-agent__failed.md` +- **Impact:** Reduces scheduler failures due to missing or malformed run artifacts. +- **Scope:** Add explicit metadata template/guardrails for all agent prompts and verify against `verify-run-artifacts` expectations. +- **Status:** needs-scoping + +### BACKLOG-015: Consolidate persistent TODO debt into actionable tracked items +- **Source:** `src/todo/` backlog sprawl, `artifacts/todos.txt` +- **Impact:** Prevents repeated rediscovery loops and improves planner/builder throughput. +- **Scope:** Define archival policy for stale TODO artifacts and create a maintained actionable TODO registry. +- **Status:** needs-scoping + +## Priority 3: Deferred + +### BACKLOG-009: Build standalone binary distribution (Node.js SEA or pkg) +- **Source:** `docs/architecture/distribution-plan.md` +- **Impact:** Removes Node.js runtime requirement for some users. +- **Scope:** Build/release pipeline work across platforms. +- **Status:** deferred + +### BACKLOG-010: Build hosted dashboard at `dashboard.torch.sh` +- **Source:** `docs/architecture/distribution-plan.md` +- **Impact:** Provides shareable dashboard access without local runtime. +- **Scope:** Hosting/domain/release operational work. +- **Status:** deferred + +### BACKLOG-011: Implement `migration-agent` for database migration auditing +- **Source:** `docs/AGENT_COVERAGE_GAPS.md` +- **Impact:** Adds migration-focused audit coverage. +- **Scope:** New weekly agent and migration verification checks. +- **Status:** deferred + +### BACKLOG-016: Implement `prompt-contract-agent` +- **Source:** `docs/AGENT_COVERAGE_GAPS.md` +- **Impact:** Dedicated ownership for prompt-contract drift prevention. +- **Scope:** New weekly agent; likely overlaps with existing prompt-maintenance/governance workflows. +- **Status:** deferred + +### BACKLOG-017: Implement `skills-agent` +- **Source:** `docs/AGENT_COVERAGE_GAPS.md` +- **Impact:** Ongoing maintenance for skill definitions and usage quality. +- **Scope:** New low-priority weekly agent. +- **Status:** deferred + +### BACKLOG-018: Implement `landing-page-agent` +- **Source:** `docs/AGENT_COVERAGE_GAPS.md` +- **Impact:** Dedicated ownership of landing-page updates and release messaging. +- **Scope:** New low-priority weekly agent. +- **Status:** deferred + +## Completed (Last 4 Weeks) +- No backlog-tracked items have been transitioned to `completed` yet. + +## Retired + +### RETIRED-001: `content-audit-agent` upload/contribution audit scope +- **Rationale:** TORCH has no upload/contribution product surface; replaced by `BACKLOG-003` scoped to real TORCH surfaces. +- **Retired:** 2026-02-21 + +### RETIRED-002: `plan-agent-architecture` proposal as a net-new pipeline request +- **Rationale:** Core pipeline agents (`proposal-triage-agent`, `plan-agent`, `builder-agent`) already exist in weekly roster and prompt set; remaining work is tracked as discrete backlog items. +- **Retired:** 2026-02-21 + +### RETIRED-003: Goose Desktop wrapper defects as direct repository backlog items +- **Rationale:** ETXTBSY and exit-code swallowing are upstream Goose wrapper defects outside this repo’s control; keep in `KNOWN_ISSUES.md`/incident notes until upstream remediation lands. +- **Retired:** 2026-02-21 diff --git a/src/cmd-rollback.mjs b/src/cmd-rollback.mjs index 129aed0..e963928 100644 --- a/src/cmd-rollback.mjs +++ b/src/cmd-rollback.mjs @@ -1,31 +1,18 @@ -import { - rollbackPrompt as _rollbackPrompt, - listPromptVersions as _listPromptVersions -} from './services/governance/index.js'; +import { rollbackPrompt, listPromptVersions } from './services/governance/index.js'; import { ExitError } from './errors.mjs'; -export async function cmdRollback(target, strategy, options = {}, deps = {}) { - // Backwards compatibility: if options is destructured directly - const { list = false } = options; - - const { - rollbackPrompt = _rollbackPrompt, - listPromptVersions = _listPromptVersions, - log = console.log, - error = console.error - } = deps; - +export async function cmdRollback(target, strategy, { list = false } = {}) { if (!target) { - error('Usage: torch-lock rollback --target [--strategy ] [--list]'); + console.error('Usage: torch-lock rollback --target [--strategy ] [--list]'); throw new ExitError(1, 'Missing target'); } if (list) { try { const versions = await listPromptVersions(target); - log(JSON.stringify(versions, null, 2)); + console.log(JSON.stringify(versions, null, 2)); } catch (e) { - error(`Failed to list versions: ${e.message}`); + console.error(`Failed to list versions: ${e.message}`); throw new ExitError(1, 'List versions failed'); } return; @@ -35,9 +22,9 @@ export async function cmdRollback(target, strategy, options = {}, deps = {}) { try { const result = await rollbackPrompt(target, effectiveStrategy); - log(JSON.stringify(result, null, 2)); + console.log(JSON.stringify(result, null, 2)); } catch (e) { - error(`Rollback failed: ${e.message}`); + console.error(`Rollback failed: ${e.message}`); throw new ExitError(1, 'Rollback failed'); } } diff --git a/src/context/CONTEXT_2026-02-21T19-48-18Z.md b/src/context/CONTEXT_2026-02-21T19-48-18Z.md new file mode 100644 index 0000000..b592053 --- /dev/null +++ b/src/context/CONTEXT_2026-02-21T19-48-18Z.md @@ -0,0 +1,17 @@ +agent: test-audit-agent +cadence: daily +session: daily-20260221T194818Z-test-audit-agent +run-start: 2026-02-21T19:48:18Z +prompt-file: src/prompts/daily/test-audit-agent.md + +Goal +- Audit test integrity and strengthen scenario-level anti-cheat coverage. + +Scope +- Inspect test/CI entry points and run audit tools. +- Fix audit signal integrity defects. +- Add regression tests for audit tooling behavior. + +Constraints +- No test weakening. +- No scheduler completion/log publication from spawned agent. diff --git a/src/context/CONTEXT_2026-02-21T20-22-32Z.md b/src/context/CONTEXT_2026-02-21T20-22-32Z.md new file mode 100644 index 0000000..13df87a --- /dev/null +++ b/src/context/CONTEXT_2026-02-21T20-22-32Z.md @@ -0,0 +1,22 @@ +--- +agent: proposal-triage-agent +cadence: weekly +session: weekly-20260221T202232Z-proposal-triage-agent +run-start: 2026-02-21T20:22:32Z +prompt-file: src/prompts/weekly/proposal-triage-agent.md +--- + +Goal +- Re-triage pending proposals, unresolved incidents/issues, and operational dead ends into a single prioritized implementation backlog. + +Scope +- Inputs reviewed: `docs/proposals/*.md`, `docs/AGENT_COVERAGE_GAPS.md`, `KNOWN_ISSUES.md`, `task-logs/daily/`, `task-logs/weekly/`, `artifacts/`, `src/todo/`, `features/`, `docs/agent-handoffs/incidents/`, previous `src/backlog/BACKLOG.md`. + +Constraints +- No implementation planning/build execution in this run. +- Preserve existing backlog IDs and classify by triage status only. +- Keep scheduler-owned completion/logging out of scope for this spawned agent run. + +Issue handling +- No new reproducible issue was discovered during this triage pass. +- Existing unresolved issues remain tracked in `KNOWN_ISSUES.md` and corresponding incident notes; no updates were required in this run. diff --git a/src/decisions/DECISIONS_2026-02-21T19-48-18Z.md b/src/decisions/DECISIONS_2026-02-21T19-48-18Z.md new file mode 100644 index 0000000..2d5e2f6 --- /dev/null +++ b/src/decisions/DECISIONS_2026-02-21T19-48-18Z.md @@ -0,0 +1,20 @@ +agent: test-audit-agent +cadence: daily +session: daily-20260221T194818Z-test-audit-agent +run-start: 2026-02-21T19:48:18Z +prompt-file: src/prompts/daily/test-audit-agent.md + +Decision +- Refactor `scripts/test-audit/run-flaky-check.mjs` to parse TAP reporter files per run and fail closed when zero outcomes are observed. + +Rationale +- Piped child-process output is environment-sensitive and produced empty matrices in this run. +- A truthful flakiness audit must not report success when no evidence was captured. + +Alternatives considered +- Keep stdout parsing and parse stderr too: still vulnerable to no-output child-process environments. +- Loosen checks and allow empty matrices: rejected as anti-cheat regression. + +Consequence +- Flaky-check now exposes missing signal as a failing audit outcome. +- Added dedicated regression test with deterministic pass/fail fixtures. diff --git a/src/decisions/DECISIONS_2026-02-21T20-22-32Z.md b/src/decisions/DECISIONS_2026-02-21T20-22-32Z.md new file mode 100644 index 0000000..be3ae00 --- /dev/null +++ b/src/decisions/DECISIONS_2026-02-21T20-22-32Z.md @@ -0,0 +1,30 @@ +--- +agent: proposal-triage-agent +cadence: weekly +session: weekly-20260221T202232Z-proposal-triage-agent +run-start: 2026-02-21T20:22:32Z +prompt-file: src/prompts/weekly/proposal-triage-agent.md +--- + +Decision +- Reclassify the implementation backlog to prioritize direct unblockers first (format script, missing event schema module, content-audit scope correction, prompt graph extraction, scheduler platform assertion alignment), while deferring lower-impact agent additions and upstream-only external defects. + +Rationale +- Current highest-friction failures are deterministic and local to this repository (`npm run format` absent, missing `src/lib/eventSchemas.js`, scheduler preflight platform mismatch). +- Coverage-gap proposals with lower immediate operational impact (`skills-agent`, `landing-page-agent`) should remain deferred. +- Goose wrapper ETXTBSY/exit-code defects are external to this repository and should be retired from implementation backlog while remaining tracked as operational known issues. + +Scoring notes (weighted: Unblock*3 + Value*2 + Scope*1 - Risk*1) +- BACKLOG-001 `npm run format`: score 14 (high unblock, low risk) -> `ready-to-plan` +- BACKLOG-002 `src/lib/eventSchemas.js`: score 13 (high unblock, bounded scope) -> `ready-to-plan` +- BACKLOG-012 scheduler platform assertion alignment: score 12 (high validation unblock) -> `ready-to-plan` +- BACKLOG-013 memory telemetry capture stabilization: score 8 (valuable but implementation uncertain) -> `needs-scoping` +- BACKLOG-016/017/018 new low-priority agent additions: score <=5 -> `deferred` + +Alternatives considered +- Keep prior backlog unchanged: rejected because it omitted active known-issue backlog items and several coverage-gap entries. +- Promote all coverage-gap agents to `needs-scoping`: rejected to avoid priority dilution and preserve execution focus for plan/builder. + +Consequences +- Plan-agent now has a tighter set of P1 items aligned to active failures. +- Deferred queue remains visible without blocking higher-value remediation. diff --git a/src/lib.mjs b/src/lib.mjs index ab7b9c3..ddb87e1 100644 --- a/src/lib.mjs +++ b/src/lib.mjs @@ -102,7 +102,13 @@ export async function cmdCheck(cadence, deps = {}) { } } - const locks = await queryLocks(relays, cadence, dateStr, namespace); + const locks = await queryLocks( + relays, + cadence, + dateStr, + namespace, + quiet ? { errorLogger: () => {}, healthLogger: () => {} } : {}, + ); const lockedAgents = [...new Set(locks.map((l) => l.agent).filter(Boolean))]; const roster = await getRoster(cadence); const rosterSet = new Set(roster); diff --git a/src/lock-publisher.mjs b/src/lock-publisher.mjs index b330232..0027877 100644 --- a/src/lock-publisher.mjs +++ b/src/lock-publisher.mjs @@ -1,4 +1,4 @@ -import { randomUUID } from 'node:crypto'; +import { randomUUID, randomInt } from 'node:crypto'; import { SimplePool } from 'nostr-tools/pool'; import { getPublishTimeoutMs, diff --git a/src/lock-utils.mjs b/src/lock-utils.mjs index 974cc06..8e68dd2 100644 --- a/src/lock-utils.mjs +++ b/src/lock-utils.mjs @@ -51,14 +51,19 @@ export async function getCompletedAgents(cadence, logDir, deps) { return completed; } -export function withTimeout(promise, timeoutMs, timeoutMessage) { - let timeoutHandle; +export function withTimeout(promise, timeoutMs, timeoutMessage = 'Operation timed out') { + let timer; const timeoutPromise = new Promise((_, reject) => { - timeoutHandle = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs); - }); - return Promise.race([promise, timeoutPromise]).finally(() => { - if (timeoutHandle) clearTimeout(timeoutHandle); + timer = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs); }); + return Promise.race([ + promise.finally(() => clearTimeout(timer)), + timeoutPromise, + ]); +} + +export function mergeRelayList(primaryRelays, fallbackRelays) { + return [...new Set([...primaryRelays, ...fallbackRelays])]; } const MAX_RANDOM = 281474976710655; // 2**48 - 1 diff --git a/src/ops.mjs b/src/ops.mjs index 5c831ad..d471aac 100644 --- a/src/ops.mjs +++ b/src/ops.mjs @@ -289,8 +289,14 @@ function configureTorch(cwd, paths, installDir, namespace, relays, hashtag) { mode: "required", retrieveSuccessMarkers: ["MEMORY_RETRIEVED"], storeSuccessMarkers: ["MEMORY_STORED"], - retrieveArtifacts: [`.scheduler-memory/latest/${cadence}/retrieve.ok`], - storeArtifacts: [`.scheduler-memory/latest/${cadence}/store.ok`] + retrieveArtifacts: [ + `.scheduler-memory/latest/${cadence}/retrieve.ok`, + `.scheduler-memory/latest/${cadence}/retrieve.json`, + ], + storeArtifacts: [ + `.scheduler-memory/latest/${cadence}/store.ok`, + `.scheduler-memory/latest/${cadence}/store.json`, + ], }; } @@ -298,6 +304,22 @@ function configureTorch(cwd, paths, installDir, namespace, relays, hashtag) { const policy = configData.scheduler.memoryPolicyByCadence[cadence]; policy.retrieveCommand = `node ${scriptPrefix}scripts/memory/retrieve.mjs`; policy.storeCommand = `node ${scriptPrefix}scripts/memory/store.mjs`; + const retrieveArtifacts = Array.isArray(policy.retrieveArtifacts) ? policy.retrieveArtifacts : []; + const storeArtifacts = Array.isArray(policy.storeArtifacts) ? policy.storeArtifacts : []; + if (!retrieveArtifacts.includes(`.scheduler-memory/latest/${cadence}/retrieve.ok`)) { + retrieveArtifacts.push(`.scheduler-memory/latest/${cadence}/retrieve.ok`); + } + if (!retrieveArtifacts.includes(`.scheduler-memory/latest/${cadence}/retrieve.json`)) { + retrieveArtifacts.push(`.scheduler-memory/latest/${cadence}/retrieve.json`); + } + if (!storeArtifacts.includes(`.scheduler-memory/latest/${cadence}/store.ok`)) { + storeArtifacts.push(`.scheduler-memory/latest/${cadence}/store.ok`); + } + if (!storeArtifacts.includes(`.scheduler-memory/latest/${cadence}/store.json`)) { + storeArtifacts.push(`.scheduler-memory/latest/${cadence}/store.json`); + } + policy.retrieveArtifacts = retrieveArtifacts; + policy.storeArtifacts = storeArtifacts; }); fs.writeFileSync(configPath, JSON.stringify(configData, null, 2), 'utf8'); diff --git a/src/prompts/roster.json b/src/prompts/roster.json index 9fe4dda..ef39f74 100644 --- a/src/prompts/roster.json +++ b/src/prompts/roster.json @@ -26,6 +26,7 @@ ], "weekly": [ "bug-reproducer-agent", + "builder-agent", "changelog-agent", "dead-code-agent", "feature-proposer-agent", @@ -33,11 +34,13 @@ "fuzz-agent", "perf-deepdive-agent", "perf-optimization-agent", + "plan-agent", "pr-review-agent", "prompt-fixer-agent", "prompt-gap-analysis-agent", "prompt-maintenance-agent", "prompt-safety-agent", + "proposal-triage-agent", "race-condition-agent", "refactor-agent", "repo-fit-agent", diff --git a/src/prompts/weekly-scheduler.md b/src/prompts/weekly-scheduler.md index 983b268..6a3944d 100644 --- a/src/prompts/weekly-scheduler.md +++ b/src/prompts/weekly-scheduler.md @@ -25,23 +25,26 @@ Canonical roster source: `src/prompts/roster.json` (`weekly` key). | # | Agent Name | Prompt File | |---|------------|-------------| | 1 | bug-reproducer-agent | `bug-reproducer-agent.md` | -| 2 | changelog-agent | `changelog-agent.md` | -| 3 | dead-code-agent | `dead-code-agent.md` | -| 4 | feature-proposer-agent | `feature-proposer-agent.md` | -| 5 | frontend-console-debug-agent | `frontend-console-debug-agent.md` | -| 6 | fuzz-agent | `fuzz-agent.md` | -| 7 | perf-deepdive-agent | `perf-deepdive-agent.md` | -| 8 | perf-optimization-agent | `perf-optimization-agent.md` | -| 9 | pr-review-agent | `pr-review-agent.md` | -| 10 | prompt-fixer-agent | `prompt-fixer-agent.md` | -| 11 | prompt-gap-analysis-agent | `prompt-gap-analysis-agent.md` | -| 12 | prompt-maintenance-agent | `prompt-maintenance-agent.md` | -| 13 | prompt-safety-agent | `prompt-safety-agent.md` | -| 14 | race-condition-agent | `race-condition-agent.md` | -| 15 | refactor-agent | `refactor-agent.md` | -| 16 | repo-fit-agent | `repo-fit-agent.md` | -| 17 | smoke-agent | `smoke-agent.md` | -| 18 | telemetry-agent | `telemetry-agent.md` | -| 19 | test-coverage-agent | `test-coverage-agent.md` | -| 20 | ui-ux-agent | `ui-ux-agent.md` | -| 21 | weekly-synthesis-agent | `weekly-synthesis-agent.md` | +| 2 | builder-agent | `builder-agent.md` | +| 3 | changelog-agent | `changelog-agent.md` | +| 4 | dead-code-agent | `dead-code-agent.md` | +| 5 | feature-proposer-agent | `feature-proposer-agent.md` | +| 6 | frontend-console-debug-agent | `frontend-console-debug-agent.md` | +| 7 | fuzz-agent | `fuzz-agent.md` | +| 8 | perf-deepdive-agent | `perf-deepdive-agent.md` | +| 9 | perf-optimization-agent | `perf-optimization-agent.md` | +| 10 | plan-agent | `plan-agent.md` | +| 11 | pr-review-agent | `pr-review-agent.md` | +| 12 | prompt-fixer-agent | `prompt-fixer-agent.md` | +| 13 | prompt-gap-analysis-agent | `prompt-gap-analysis-agent.md` | +| 14 | prompt-maintenance-agent | `prompt-maintenance-agent.md` | +| 15 | prompt-safety-agent | `prompt-safety-agent.md` | +| 16 | proposal-triage-agent | `proposal-triage-agent.md` | +| 17 | race-condition-agent | `race-condition-agent.md` | +| 18 | refactor-agent | `refactor-agent.md` | +| 19 | repo-fit-agent | `repo-fit-agent.md` | +| 20 | smoke-agent | `smoke-agent.md` | +| 21 | telemetry-agent | `telemetry-agent.md` | +| 22 | test-coverage-agent | `test-coverage-agent.md` | +| 23 | ui-ux-agent | `ui-ux-agent.md` | +| 24 | weekly-synthesis-agent | `weekly-synthesis-agent.md` | diff --git a/src/prompts/weekly/builder-agent.md b/src/prompts/weekly/builder-agent.md new file mode 100644 index 0000000..ceeb614 --- /dev/null +++ b/src/prompts/weekly/builder-agent.md @@ -0,0 +1,119 @@ +If no work is required, exit without making changes. + +> **Shared contract (required):** Follow [`Scheduler Flow → Shared Agent Run Contract`](../scheduler-flow.md#shared-agent-run-contract-required-for-all-spawned-agents) and [`Scheduler Flow → Canonical artifact paths`](../scheduler-flow.md#canonical-artifact-paths) before and during this run. + +## Required startup + artifacts + memory + issue capture + +- Baseline reads (required, before implementation): `AGENTS.md`, `CLAUDE.md`, `KNOWN_ISSUES.md`, and `docs/agent-handoffs/README.md`. +- Run artifacts (required): update or explicitly justify omission for `src/context/`, `src/todo/`, `src/decisions/`, and `src/test_logs/`. +- Unresolved issue handling (required): if unresolved/reproducible findings remain, update `KNOWN_ISSUES.md` and add or update an incidents note in `docs/agent-handoffs/incidents/`. +- Memory contract (required): execute configured memory retrieval before implementation and configured memory storage after implementation, preserving scheduler evidence markers/artifacts. +- Completion ownership (required): **do not** run `lock:complete` and **do not** create final `task-logs//____completed.md` or `__failed.md`; spawned agents hand results back to the scheduler, and the scheduler owns completion publishing/logging. + +You are: **builder-agent**, a weekly agent that implements the oldest approved plan from the plans directory. + +Mission: Execute an implementation plan by making the code changes described in it, running the specified tests, and verifying all acceptance criteria are met. + +--- + +## Scope + +In scope: +- Reading `src/plans/PLAN-*.md` to find the oldest plan with status `planned` +- Implementing the exact changes described in the plan +- Running all test commands specified in the plan +- Verifying all acceptance criteria +- Updating plan status (`planned` → `completed` or `blocked`) +- Updating backlog item status after completion + +Out of scope: +- Making changes not described in the plan +- Expanding scope beyond what the plan specifies +- Skipping steps or tests listed in the plan +- Creating new plans (that's `plan-agent`'s job) +- Triaging or prioritizing items (that's `proposal-triage-agent`'s job) +- Modifying agent prompts outside of what the plan specifies (use `governance-agent` for prompt changes) + +--- + +## Builder rules + +1. **Follow the plan exactly** — Do not improvise. If the plan says "add line X to file Y", do that. Do not add bonus refactors, extra comments, or "while I'm here" improvements. +2. **Don't expand scope** — Only make changes listed in the plan's implementation steps. If you discover additional work is needed, document it as a new backlog item instead of doing it. +3. **Fail loudly** — If a step cannot be completed (file doesn't exist, test fails, dependency missing), stop and document why. Do not skip steps. +4. **Test everything** — Run every test command in the plan. Run `npm test` and `npm run lint` as final validation even if not explicitly listed. +5. **One plan per cycle** — Complete one plan fully before considering another. If the plan cannot be completed, mark it `blocked` and stop. + +--- + +## Weekly workflow + +1. Read `src/plans/` directory and find the oldest plan file with `Status: planned` +2. If no `planned` plans exist, exit without changes +3. Read the full plan file +4. Read all files referenced in the plan's "Files Modified" and "Files Created" sections +5. Read `KNOWN_ISSUES.md` for any blockers that might affect implementation +6. **Claim the plan**: Update the plan file frontmatter to `Status: in-progress` and add `started: ` +7. **Execute steps sequentially**: Follow each implementation step in order +8. **Validate after each step**: Run the step's test command if one is specified +9. **Run acceptance criteria**: Execute every acceptance criterion command +10. **Final validation**: Run `npm test` and `npm run lint` +11. **Update plan status**: + - If all acceptance criteria pass: set `Status: completed`, add `completed: ` + - If any criterion fails: set `Status: blocked`, add `blocked_reason: ` +12. **Update backlog**: If completed, update the corresponding item in `src/backlog/BACKLOG.md` to `completed` + +--- + +## Handling failures + +If any implementation step or acceptance criterion fails: + +1. **Stop implementing** — Do not continue to subsequent steps +2. **Revert partial changes** if they would leave the codebase in a broken state (tests failing, lint errors) +3. **Update plan status** to `blocked` with: + ``` + Status: blocked + blocked_reason: + blocked_at_step: + blocked_timestamp: + ``` +4. **Document the failure**: + - If it's a new issue: add to `KNOWN_ISSUES.md` + - Write incident note to `docs/agent-handoffs/incidents/` +5. **Do NOT mark backlog item as completed** +6. **Do NOT attempt workarounds** that deviate from the plan + +The `proposal-triage-agent` will pick up the blocked plan in its next cycle and either re-scope the backlog item or identify a prerequisite fix. + +--- + +## Relationship to feature-proposer-agent + +The existing `feature-proposer-agent` creates single-file features in `features/`. This agent (`builder-agent`) executes multi-step plans that may modify existing files. Both agents coexist: + +| Aspect | feature-proposer-agent | builder-agent | +|--------|----------------------|---------------| +| Input | Own analysis | Explicit plan from plan-agent | +| Scope | Single new file in `features/` | Any files specified in plan | +| Modifications | New files only | New + existing files | +| Validation | Self-verified | Acceptance criteria from plan | +| Autonomy | High (chooses what to build) | Low (follows plan exactly) | + +--- + +## Output expectations + +- Code changes as specified in the implementation plan +- Updated plan file with completion status and timestamps +- Updated `src/backlog/BACKLOG.md` with item status +- Test log in `src/test_logs/TEST_LOG_.md` with all test results +- Decision artifact in `src/decisions/DECISIONS_.md` +- Context artifact in `src/context/CONTEXT_.md` + +FAILURE MODES +- If preconditions are not met, stop. +- If no plans with `Status: planned` exist, do nothing. +- If the plan references files that don't exist, mark plan as `blocked` and stop. +- If tests fail after implementation, revert changes, mark plan as `blocked`, and document the failure. +- If specific resources (files, URLs) are unavailable, log the error and mark plan as `blocked`. diff --git a/src/prompts/weekly/plan-agent.md b/src/prompts/weekly/plan-agent.md new file mode 100644 index 0000000..5a246ef --- /dev/null +++ b/src/prompts/weekly/plan-agent.md @@ -0,0 +1,131 @@ +If no work is required, exit without making changes. + +> **Shared contract (required):** Follow [`Scheduler Flow → Shared Agent Run Contract`](../scheduler-flow.md#shared-agent-run-contract-required-for-all-spawned-agents) and [`Scheduler Flow → Canonical artifact paths`](../scheduler-flow.md#canonical-artifact-paths) before and during this run. + +## Required startup + artifacts + memory + issue capture + +- Baseline reads (required, before implementation): `AGENTS.md`, `CLAUDE.md`, `KNOWN_ISSUES.md`, and `docs/agent-handoffs/README.md`. +- Run artifacts (required): update or explicitly justify omission for `src/context/`, `src/todo/`, `src/decisions/`, and `src/test_logs/`. +- Unresolved issue handling (required): if unresolved/reproducible findings remain, update `KNOWN_ISSUES.md` and add or update an incidents note in `docs/agent-handoffs/incidents/`. +- Memory contract (required): execute configured memory retrieval before implementation and configured memory storage after implementation, preserving scheduler evidence markers/artifacts. +- Completion ownership (required): **do not** run `lock:complete` and **do not** create final `task-logs//____completed.md` or `__failed.md`; spawned agents hand results back to the scheduler, and the scheduler owns completion publishing/logging. + +You are: **plan-agent**, a weekly agent that creates detailed implementation plans for the highest-priority backlog item. + +Mission: Take the top `ready-to-plan` item from the backlog and produce a concrete, step-by-step implementation plan that a builder agent can execute without ambiguity. + +--- + +## Scope + +In scope: +- Reading `src/backlog/BACKLOG.md` to find the top `ready-to-plan` item +- Reading the original proposal or source document for full context +- Reading relevant codebase files to understand integration points +- Producing a single implementation plan in `src/plans/` +- Updating the backlog item status from `ready-to-plan` to `planned` +- If no `ready-to-plan` items exist, scoping a `needs-scoping` item (update its status to `ready-to-plan`) + +Out of scope: +- Implementing code changes (that's `builder-agent`'s job) +- Triaging or prioritizing items (that's `proposal-triage-agent`'s job) +- Planning more than one item per cycle +- Making changes beyond the plan file and backlog status update + +--- + +## Planning rules + +1. **One plan per cycle** — Pick the single highest-priority `ready-to-plan` item. Do not plan multiple items. +2. **Concrete file paths** — Every step must name the exact file(s) to create or modify. +3. **Testable acceptance** — Every acceptance criterion must be verifiable by running a command (e.g., `npm test`, `npm run lint`, a specific script). +4. **No ambiguity** — A builder agent reading only this plan should be able to implement it without guessing intent. +5. **Scope guard** — If the item is too large for a single builder cycle (more than ~10 file changes), break it into sub-plans and update the backlog with sub-items. + +--- + +## Weekly workflow + +1. Read `src/backlog/BACKLOG.md` +2. Select the top `ready-to-plan` item (highest priority, earliest backlog ID) +3. If no `ready-to-plan` items exist: + a. Look for `needs-scoping` items + b. Investigate scope for the highest-priority one + c. If scopable, update its status to `ready-to-plan` and continue + d. If not scopable, document why in decisions artifact and exit +4. Read all source documents referenced by the backlog item +5. Read all relevant codebase files to understand current state +6. Write implementation plan to `src/plans/PLAN-.md` +7. Update the backlog item status: `ready-to-plan` → `planned (see src/plans/PLAN-.md)` +8. Write planning rationale to `src/decisions/DECISIONS_.md` + +--- + +## Plan file format + +Write `src/plans/PLAN-.md` using this structure: + +```markdown +# Implementation Plan: — + +## Backlog Reference +- **ID:** <backlog-id> +- **Source:** <original source document or issue> +- **Priority:** <P1/P2/P3> +- **Status:** planned + +## Objective +<1-2 sentence description of what this plan achieves> + +## Analysis +<Key findings from reading the codebase — what exists today, what's missing, +what the integration points are> + +## Implementation Steps + +### Step 1: <action> +- **File:** <exact file path> +- **Change:** <what to add/modify/remove> +- **Test:** <command to verify this step> + +### Step 2: <action> +... + +## Files Modified +- <file path> (<brief description of change>) + +## Files Created +- <file path> (<brief description>) + +## Acceptance Criteria +1. <Verifiable criterion with command> +2. <Verifiable criterion with command> +... + +## Test Strategy +- <What tests to run and in what order> + +## Risk Assessment +- <Potential issues and how to handle them> + +## Dependencies +- <Other backlog items or external factors this depends on> + +## Complexity +- **Simple** (< 5 file changes) | **Medium** (5-10 changes) | **Complex** (10+ changes, may need sub-plans) +``` + +--- + +## Output expectations + +- A plan file in `src/plans/PLAN-<backlog-id>.md` +- Updated `src/backlog/BACKLOG.md` with item status changed to `planned` +- Decision artifact in `src/decisions/DECISIONS_<timestamp>.md` +- Context artifact in `src/context/CONTEXT_<timestamp>.md` + +FAILURE MODES +- If preconditions are not met, stop. +- If no changes are needed (no `ready-to-plan` or `needs-scoping` items), do nothing. +- If the backlog file doesn't exist, document this in decisions and exit. +- If specific resources (files, URLs) are unavailable, log the error and skip. diff --git a/src/prompts/weekly/proposal-triage-agent.md b/src/prompts/weekly/proposal-triage-agent.md new file mode 100644 index 0000000..582c04d --- /dev/null +++ b/src/prompts/weekly/proposal-triage-agent.md @@ -0,0 +1,126 @@ +If no work is required, exit without making changes. + +> **Shared contract (required):** Follow [`Scheduler Flow → Shared Agent Run Contract`](../scheduler-flow.md#shared-agent-run-contract-required-for-all-spawned-agents) and [`Scheduler Flow → Canonical artifact paths`](../scheduler-flow.md#canonical-artifact-paths) before and during this run. + +## Required startup + artifacts + memory + issue capture + +- Baseline reads (required, before implementation): `AGENTS.md`, `CLAUDE.md`, `KNOWN_ISSUES.md`, and `docs/agent-handoffs/README.md`. +- Run artifacts (required): update or explicitly justify omission for `src/context/`, `src/todo/`, `src/decisions/`, and `src/test_logs/`. +- Unresolved issue handling (required): if unresolved/reproducible findings remain, update `KNOWN_ISSUES.md` and add or update an incidents note in `docs/agent-handoffs/incidents/`. +- Memory contract (required): execute configured memory retrieval before implementation and configured memory storage after implementation, preserving scheduler evidence markers/artifacts. +- Completion ownership (required): **do not** run `lock:complete` and **do not** create final `task-logs/<cadence>/<timestamp>__<agent-name>__completed.md` or `__failed.md`; spawned agents hand results back to the scheduler, and the scheduler owns completion publishing/logging. + +You are: **proposal-triage-agent**, a weekly agent that evaluates pending proposals, identifies dead ends, and produces a prioritized implementation backlog. + +Mission: Synthesize all pending work items — proposals, coverage gaps, dead-end agents, stale artifacts, and unresolved incidents — into a single prioritized backlog that downstream agents (`plan-agent`, `builder-agent`) can consume. + +--- + +## Scope + +In scope: +- Reading and evaluating `docs/proposals/*.md` for implementation status +- Reading `docs/AGENT_COVERAGE_GAPS.md` for unimplemented agents +- Reading `KNOWN_ISSUES.md` for blocking issues that need tooling fixes +- Scanning `task-logs/daily/` and `task-logs/weekly/` for failed agent patterns +- Scanning `artifacts/` for reports that haven't driven action +- Consolidating `src/todo/` scattered files into backlog items +- Evaluating `features/` outputs from `feature-proposer-agent` +- Reading `docs/agent-handoffs/incidents/` for unresolved incidents +- Carrying forward items from previous `src/backlog/BACKLOG.md` +- Retiring dead-end items with documented rationale + +Out of scope: +- Creating implementation plans (that's `plan-agent`'s job) +- Making code changes (that's `builder-agent`'s job) +- Modifying agent prompts (that's `governance-agent`'s job) + +--- + +## Triage criteria + +Score each item on four dimensions: + +| Dimension | Weight | Question | +|-----------|--------|----------| +| **Unblock** | 3x | Does this unblock a currently-broken agent or workflow? | +| **Value** | 2x | Does this deliver user-visible value or reduce failure rate? | +| **Scope** | 1x | Is the implementation scope clear and bounded? | +| **Risk** | -1x | Does this require architectural changes or new dependencies? | + +### Status values + +- `ready-to-plan` — Clear scope, no blockers, high value. Plan-agent should pick this up. +- `needs-scoping` — Valuable but unclear implementation. Needs investigation before planning. +- `deferred` — Has value but lower priority or blocked by dependencies. +- `planned` — Plan-agent has created an implementation plan (set by plan-agent, not triage). +- `in-progress` — Builder-agent is implementing (set by builder-agent, not triage). +- `completed` — Implemented and verified (set by builder-agent, not triage). +- `retired` — Not worth pursuing. Must include rationale. + +### Retirement rules + +Retire an item (don't just defer) when: +- The feature it depends on doesn't exist and isn't planned +- The proposal has been superseded by a different approach +- The cost clearly exceeds the benefit with no path to improvement +- The item has been deferred for 4+ consecutive triage cycles with no progress + +--- + +## Weekly workflow + +1. Read all inputs: `docs/proposals/`, `docs/AGENT_COVERAGE_GAPS.md`, `KNOWN_ISSUES.md`, `task-logs/`, `artifacts/`, `src/todo/`, `features/`, `docs/agent-handoffs/incidents/` +2. Read previous `src/backlog/BACKLOG.md` if it exists +3. Evaluate each pending item against triage criteria +4. Score and rank all items +5. Write updated `src/backlog/BACKLOG.md` with the format below +6. Write triage rationale to `src/decisions/DECISIONS_<timestamp>.md` + +--- + +## Output format + +Write `src/backlog/BACKLOG.md` using this structure: + +```markdown +# Implementation Backlog — <YYYY-MM-DD> + +## Triage Summary +- Items evaluated: <N> +- New items added: <N> +- Items retired: <N> +- Items promoted to ready: <N> + +## Priority 1: Ready to Plan + +### BACKLOG-<NNN>: <title> +- **Source:** <where this item came from> +- **Impact:** <what it unblocks or improves> +- **Scope:** <brief description of implementation size> +- **Status:** ready-to-plan +- **Acceptance:** <how to verify completion> + +## Priority 2: Needs Scoping + +## Priority 3: Deferred + +## Completed (Last 4 Weeks) + +## Retired +``` + +Preserve existing backlog IDs. Assign new sequential IDs for new items (starting after the highest existing ID). + +--- + +## Output expectations + +- Updated `src/backlog/BACKLOG.md` with all items evaluated and prioritized +- Decision artifact in `src/decisions/DECISIONS_<timestamp>.md` explaining triage rationale +- Context artifact in `src/context/CONTEXT_<timestamp>.md` + +FAILURE MODES +- If preconditions are not met, stop. +- If no changes are needed, do nothing. +- If specific resources (files, URLs) are unavailable, log the error and skip. diff --git a/src/services/governance/index.js b/src/services/governance/index.js index dd3290b..87f0974 100644 --- a/src/services/governance/index.js +++ b/src/services/governance/index.js @@ -220,8 +220,9 @@ export async function applyProposal(id) { // Git Commit (optional) try { - // We use execFileSync for git operations to avoid command injection - // Ensure we are in repo root? cwd is repo root. + // We use execFileSync for git operations to avoid command injection. + // Explicitly using the array form ensures arguments are passed directly to the spawned process + // without shell interpretation, preventing command injection attacks via 'meta.target' or 'commitMsg'. execFileSync('git', ['add', meta.target]); const commitMsg = `feat(prompts): apply proposal ${id} by ${meta.author}`; execFileSync('git', ['commit', '-m', commitMsg]); diff --git a/src/test_logs/TEST_LOG_2026-02-21T19-48-18Z.md b/src/test_logs/TEST_LOG_2026-02-21T19-48-18Z.md new file mode 100644 index 0000000..f0eaab9 --- /dev/null +++ b/src/test_logs/TEST_LOG_2026-02-21T19-48-18Z.md @@ -0,0 +1,26 @@ +agent: test-audit-agent +cadence: daily +session: daily-20260221T194818Z-test-audit-agent +run-start: 2026-02-21T19:48:18Z +prompt-file: src/prompts/daily/test-audit-agent.md + +Command: node scripts/memory/retrieve.mjs +Result: pass +Notes: emitted MEMORY_RETRIEVED; artifact marker at .scheduler-memory/latest/daily/retrieve.ok + +Command: node scripts/test-audit/run-static-analysis.mjs --output-dir reports/test-audit +Result: pass +Notes: wrote reports/test-audit/suspicious-tests.json + +Command: node scripts/test-audit/run-flaky-check.mjs --output-dir reports/test-audit test/scheduler-preflight-lock.e2e.test.mjs test/scheduler-lock-failure-schema.contract.test.mjs test/lock-ops-publish-retry.test.mjs +Result: pass +Notes: wrote flakiness-matrix.json + flakiness-runs.json + flaky-run-*.tap + +Command: node --test test/test-audit-run-flaky-check.test.mjs +Result: pass +Notes: regression test for deterministic flakiness signal capture + +Command: node test/scheduler-preflight-lock.e2e.test.mjs +Result: fail +Failure-ID: KNOWN-ISSUE-scheduler-preflight-platform +Notes: expected platform unknown vs actual codex; issue tracked in KNOWN_ISSUES.md and incidents note. diff --git a/src/test_logs/TEST_LOG_2026-02-21T20-22-32Z.md b/src/test_logs/TEST_LOG_2026-02-21T20-22-32Z.md new file mode 100644 index 0000000..abd032e --- /dev/null +++ b/src/test_logs/TEST_LOG_2026-02-21T20-22-32Z.md @@ -0,0 +1,29 @@ +--- +agent: proposal-triage-agent +cadence: weekly +session: weekly-20260221T202232Z-proposal-triage-agent +run-start: 2026-02-21T20:22:32Z +prompt-file: src/prompts/weekly/proposal-triage-agent.md +--- + +# Test Log — proposal-triage-agent — 2026-02-21T20:22:32Z + +- Command: `SCHEDULER_CADENCE=weekly SCHEDULER_AGENT=proposal-triage-agent SCHEDULER_PROMPT_PATH=src/prompts/weekly/proposal-triage-agent.md SCHEDULER_RUN_ID=manual-proposal-triage-20260221T152500Z node scripts/memory/retrieve.mjs` + - Result: pass (`MEMORY_RETRIEVED`) + - Notes: Retrieval evidence refreshed under `.scheduler-memory/latest/weekly/`. + +- Command: `SCHEDULER_CADENCE=weekly SCHEDULER_AGENT=proposal-triage-agent SCHEDULER_PROMPT_PATH=src/prompts/weekly/proposal-triage-agent.md SCHEDULER_RUN_ID=manual-proposal-triage-20260221T152500Z SCHEDULER_MEMORY_FILE=memory-updates/2026-02-21T20-22-32Z__proposal-triage-agent.md node scripts/memory/store.mjs` + - Result: pass (`MEMORY_STORED`) + - Notes: Store evidence refreshed under `.scheduler-memory/latest/weekly/`. + +- Command: source review across triage inputs (`docs/proposals`, `docs/AGENT_COVERAGE_GAPS.md`, `KNOWN_ISSUES.md`, `task-logs`, `artifacts`, `src/todo`, `features`, `docs/agent-handoffs/incidents`, existing backlog) + - Result: pass + - Notes: Evidence collection for backlog reprioritization. + +- Command: `ls src/lib/eventSchemas.js` + - Result: fail (`MISSING src/lib/eventSchemas.js`) + - Notes: Confirms backlog blocker remains unresolved. + +- Command: `node -e "const p=require('./package.json'); ..."` and script-key check + - Result: fail (`format` / `format:check` scripts absent) + - Notes: Confirms BACKLOG-001 remains actionable. diff --git a/src/todo/TODO_2026-02-20T03-00-00Z.md b/src/todo/TODO_2026-02-20T03-00-00Z.md index d8f1693..368902b 100644 --- a/src/todo/TODO_2026-02-20T03-00-00Z.md +++ b/src/todo/TODO_2026-02-20T03-00-00Z.md @@ -9,7 +9,6 @@ - [x] Update `README.md` to include `scheduler:daily`, `scheduler:weekly`, and `lock:complete`. - [x] Update `TORCH.md` to include `scheduler:daily`, `scheduler:weekly`, and `lock:complete`. - [x] Verify updated commands run correctly (where applicable/safe). - - Fixed syntax errors and circular dependencies in `src/lock-ops.mjs`, `src/lock-utils.mjs`, `src/lock-publisher.mjs`, and `src/relay-health.mjs` that were preventing commands from running. ## Blocked - None. diff --git a/src/todo/TODO_2026-02-21T19-48-18Z.md b/src/todo/TODO_2026-02-21T19-48-18Z.md new file mode 100644 index 0000000..acbb80c --- /dev/null +++ b/src/todo/TODO_2026-02-21T19-48-18Z.md @@ -0,0 +1,17 @@ +agent: test-audit-agent +cadence: daily +session: daily-20260221T194818Z-test-audit-agent +run-start: 2026-02-21T19:48:18Z +prompt-file: src/prompts/daily/test-audit-agent.md + +Completed +- Read required baseline files and scheduler shared contract. +- Executed memory retrieval command and verified retrieval marker/artifacts. +- Ran static-analysis and flaky-check audit tools. +- Fixed flaky-check deterministic signal capture. +- Added regression test and deterministic fixtures. +- Reproduced unresolved scheduler platform mismatch and updated issue/incidents tracking. + +Remaining +- Execute memory store workflow. +- Final summary handoff to scheduler. diff --git a/src/todo/TODO_2026-02-21T20-22-32Z.md b/src/todo/TODO_2026-02-21T20-22-32Z.md new file mode 100644 index 0000000..a47a646 --- /dev/null +++ b/src/todo/TODO_2026-02-21T20-22-32Z.md @@ -0,0 +1,22 @@ +--- +agent: proposal-triage-agent +cadence: weekly +session: weekly-20260221T202232Z-proposal-triage-agent +run-start: 2026-02-21T20:22:32Z +prompt-file: src/prompts/weekly/proposal-triage-agent.md +--- + +# TODO — proposal-triage-agent — 2026-02-21T20:22:32Z + +## Completed +- [x] Read required startup files and scheduler shared contract. +- [x] Executed weekly memory retrieval command and checked retrieval evidence. +- [x] Reviewed proposals, coverage gaps, known issues, incidents, task-log failures, artifacts, TODO debt, and existing backlog. +- [x] Re-scored and reprioritized backlog items. +- [x] Updated `src/backlog/BACKLOG.md` with preserved IDs and new sequential IDs. +- [x] Wrote context/decision/test-log artifacts for this run. +- [x] Wrote memory update content for this run. +- [x] Executed weekly memory store command and verified storage evidence marker. + +## Pending +- [ ] Hand off results to scheduler-owned completion pipeline. diff --git a/task-logs/daily/.scheduler-run-state.json b/task-logs/daily/.scheduler-run-state.json deleted file mode 100644 index 92d55d6..0000000 --- a/task-logs/daily/.scheduler-run-state.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "run_date": "2026-02-20", - "lock_deferral": null -} diff --git a/task-logs/daily/2026-02-20T21-46-47Z__scheduler-update-agent__failed.md b/task-logs/daily/2026-02-20T21-46-47Z__scheduler-update-agent__failed.md deleted file mode 100644 index 374e298..0000000 --- a/task-logs/daily/2026-02-20T21-46-47Z__scheduler-update-agent__failed.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -cadence: daily -agent: scheduler-update-agent -status: failed -reason: 'Prompt/handoff execution failed' -detail: 'Handoff callback failed.' -created_at: 2026-02-20T21:46:47.102Z -timestamp: 2026-02-20T21:46:47.102Z -platform: 'jules' -failure_category: 'execution_error' -failure_class: 'prompt_validation_error' ---- -# Scheduler failed -- reason: Prompt/handoff execution failed -- detail: Handoff callback failed. -- platform: jules -- failure_category: execution_error -- failure_class: prompt_validation_error \ No newline at end of file diff --git a/task-logs/daily/2026-02-21T19-46-36Z__style-agent__failed.md b/task-logs/daily/2026-02-21T19-46-36Z__style-agent__failed.md new file mode 100644 index 0000000..a74cd46 --- /dev/null +++ b/task-logs/daily/2026-02-21T19-46-36Z__style-agent__failed.md @@ -0,0 +1,20 @@ +--- +cadence: daily +agent: style-agent +status: failed +reason: 'Prompt/handoff execution failed' +detail: 'Command failed: AGENT_PLATFORM=codex node scripts/agent/run-selected-prompt.mjs (stream disconnected before completion)' +created_at: 2026-02-21T19:46:36Z +timestamp: 2026-02-21T19:46:36Z +platform: 'codex' +failure_category: 'execution_error' +failure_class: 'prompt_validation_error' +prompt_path: 'src/prompts/daily/style-agent.md' +--- +# Scheduler failed +- reason: Prompt/handoff execution failed +- detail: Command failed: AGENT_PLATFORM=codex node scripts/agent/run-selected-prompt.mjs (stream disconnected before completion) +- platform: codex +- failure_category: execution_error +- failure_class: prompt_validation_error +- prompt: src/prompts/daily/style-agent.md diff --git a/task-logs/daily/2026-02-21T19-55-27Z__test-audit-agent__completed.md b/task-logs/daily/2026-02-21T19-55-27Z__test-audit-agent__completed.md new file mode 100644 index 0000000..7b82ccf --- /dev/null +++ b/task-logs/daily/2026-02-21T19-55-27Z__test-audit-agent__completed.md @@ -0,0 +1,18 @@ +--- +cadence: daily +agent: test-audit-agent +status: completed +reason: 'Completed successfully' +detail: 'Prompt executed, memory retrieval/storage evidence validated, lint passed, and completion published.' +created_at: 2026-02-21T19:55:27Z +timestamp: 2026-02-21T19:55:27Z +platform: 'codex' +prompt_path: 'src/prompts/daily/test-audit-agent.md' +memory_file: 'memory-updates/2026-02-21T19-48-22Z__test-audit-agent.md' +--- +# Scheduler completed +- reason: Completed successfully +- detail: Prompt executed, memory retrieval/storage evidence validated, lint passed, and completion published. +- platform: codex +- prompt: src/prompts/daily/test-audit-agent.md +- memory_file: memory-updates/2026-02-21T19-48-22Z__test-audit-agent.md diff --git a/task-logs/weekly/2026-02-21T20-25-17Z__proposal-triage-agent__completed.md b/task-logs/weekly/2026-02-21T20-25-17Z__proposal-triage-agent__completed.md new file mode 100644 index 0000000..8487be3 --- /dev/null +++ b/task-logs/weekly/2026-02-21T20-25-17Z__proposal-triage-agent__completed.md @@ -0,0 +1,18 @@ +--- +cadence: weekly +agent: proposal-triage-agent +status: completed +reason: 'Completed successfully' +detail: 'Prompt executed, memory retrieval/storage evidence validated, lint passed, and completion published.' +created_at: 2026-02-21T20:25:17Z +timestamp: 2026-02-21T20:25:17Z +platform: 'codex' +prompt_path: 'src/prompts/weekly/proposal-triage-agent.md' +memory_file: 'memory-updates/2026-02-21T20-22-32Z__proposal-triage-agent.md' +--- +# Scheduler completed +- reason: Completed successfully +- detail: Prompt executed, memory retrieval/storage evidence validated, lint passed, and completion published. +- platform: codex +- prompt: src/prompts/weekly/proposal-triage-agent.md +- memory_file: memory-updates/2026-02-21T20-22-32Z__proposal-triage-agent.md diff --git a/task-logs/weekly/2026-02-21T20-27-39Z__repo-fit-agent__completed.md b/task-logs/weekly/2026-02-21T20-27-39Z__repo-fit-agent__completed.md new file mode 100644 index 0000000..15f96ad --- /dev/null +++ b/task-logs/weekly/2026-02-21T20-27-39Z__repo-fit-agent__completed.md @@ -0,0 +1,18 @@ +--- +cadence: weekly +agent: repo-fit-agent +status: completed +reason: 'Completed successfully' +detail: 'Prompt executed, memory retrieval/storage evidence validated, lint passed, and completion published.' +created_at: 2026-02-21T20:27:39Z +timestamp: 2026-02-21T20:27:39Z +platform: 'linux' +prompt_path: 'src/prompts/weekly/repo-fit-agent.md' +memory_file: 'memory-update.md' +--- +# Scheduler completed +- reason: Completed successfully +- detail: Prompt executed, memory retrieval/storage evidence validated, lint passed, and completion published. +- platform: linux +- prompt: src/prompts/weekly/repo-fit-agent.md +- memory_file: memory-update.md diff --git a/test/cmd-rollback.test.mjs b/test/cmd-rollback.test.mjs deleted file mode 100644 index 9288ef6..0000000 --- a/test/cmd-rollback.test.mjs +++ /dev/null @@ -1,95 +0,0 @@ -import { describe, it } from 'node:test'; -import assert from 'node:assert'; -import { cmdRollback } from '../src/cmd-rollback.mjs'; -import { ExitError } from '../src/errors.mjs'; - -describe('cmdRollback', () => { - it('should throw ExitError if target is missing', async () => { - const deps = { - error: () => {}, - }; - await assert.rejects( - async () => cmdRollback(undefined, undefined, {}, deps), - (err) => err instanceof ExitError && err.code === 1 && err.message === 'Missing target' - ); - }); - - it('should list versions when list option is true', async () => { - const logs = []; - const mockVersions = [{ hash: 'abc', archivedAt: '2023-01-01' }]; - const deps = { - listPromptVersions: async (target) => { - assert.strictEqual(target, 'foo.md'); - return mockVersions; - }, - log: (msg) => logs.push(msg), - error: () => {}, - }; - - await cmdRollback('foo.md', undefined, { list: true }, deps); - assert.strictEqual(logs.length, 1); - assert.deepStrictEqual(JSON.parse(logs[0]), mockVersions); - }); - - it('should throw ExitError if list versions fails', async () => { - const deps = { - listPromptVersions: async () => { throw new Error('fail'); }, - log: () => {}, - error: () => {}, // suppress error output - }; - - await assert.rejects( - async () => cmdRollback('foo.md', undefined, { list: true }, deps), - (err) => err instanceof ExitError && err.code === 1 && err.message === 'List versions failed' - ); - }); - - it('should rollback to latest if strategy is missing', async () => { - const logs = []; - const mockResult = { success: true, restored: 'HEAD' }; - const deps = { - rollbackPrompt: async (target, strategy) => { - assert.strictEqual(target, 'foo.md'); - assert.strictEqual(strategy, 'latest'); - return mockResult; - }, - log: (msg) => logs.push(msg), - error: () => {}, - }; - - await cmdRollback('foo.md', undefined, {}, deps); - assert.strictEqual(logs.length, 1); - assert.deepStrictEqual(JSON.parse(logs[0]), mockResult); - }); - - it('should rollback to specific strategy', async () => { - const logs = []; - const mockResult = { success: true, restored: 'hash123' }; - const deps = { - rollbackPrompt: async (target, strategy) => { - assert.strictEqual(target, 'foo.md'); - assert.strictEqual(strategy, 'hash123'); - return mockResult; - }, - log: (msg) => logs.push(msg), - error: () => {}, - }; - - await cmdRollback('foo.md', 'hash123', {}, deps); - assert.strictEqual(logs.length, 1); - assert.deepStrictEqual(JSON.parse(logs[0]), mockResult); - }); - - it('should throw ExitError if rollback fails', async () => { - const deps = { - rollbackPrompt: async () => { throw new Error('fail'); }, - log: () => {}, - error: () => {}, - }; - - await assert.rejects( - async () => cmdRollback('foo.md', 'latest', {}, deps), - (err) => err instanceof ExitError && err.message === 'Rollback failed' - ); - }); -}); diff --git a/test/fixtures/flaky-check/fail.fixture.test.mjs b/test/fixtures/flaky-check/fail.fixture.test.mjs new file mode 100644 index 0000000..1ec5b57 --- /dev/null +++ b/test/fixtures/flaky-check/fail.fixture.test.mjs @@ -0,0 +1,6 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; + +test('fail fixture stays red', () => { + assert.equal(1, 2); +}); diff --git a/test/fixtures/flaky-check/pass.fixture.test.mjs b/test/fixtures/flaky-check/pass.fixture.test.mjs new file mode 100644 index 0000000..4c96b53 --- /dev/null +++ b/test/fixtures/flaky-check/pass.fixture.test.mjs @@ -0,0 +1,6 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; + +test('pass fixture stays green', () => { + assert.equal(2 + 2, 4); +}); diff --git a/test/governance-security-impl.mjs b/test/governance-security-impl.mjs new file mode 100644 index 0000000..acdc109 --- /dev/null +++ b/test/governance-security-impl.mjs @@ -0,0 +1,111 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { execSync } from 'node:child_process'; +import os from 'node:os'; +import { fileURLToPath } from 'node:url'; + +// Calculate paths relative to this file +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(__dirname, '..'); +const governanceServicePath = path.resolve(repoRoot, 'src/services/governance/index.js'); + +async function runTest() { + // Create temp dir + const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'torch-gov-test-')); + console.log(`Using temp dir: ${tempDir}`); + + // Setup git repo + execSync('git init', { cwd: tempDir, stdio: 'ignore' }); + execSync('git config user.name "Test User"', { cwd: tempDir, stdio: 'ignore' }); + execSync('git config user.email "test@example.com"', { cwd: tempDir, stdio: 'ignore' }); + + // Create required directory structure + await fs.mkdir(path.join(tempDir, 'src/prompts/daily'), { recursive: true }); + await fs.mkdir(path.join(tempDir, 'src/prompts/weekly'), { recursive: true }); + // Proposals dir is created automatically by ensureDirs() but allowlist check needs prompts dir + + // Create a target file + const targetFile = 'src/prompts/daily/test-agent.md'; + const absoluteTarget = path.join(tempDir, targetFile); + await fs.writeFile(absoluteTarget, 'Original Content'); + + // Commit initial state so we have a HEAD + execSync(`git add ${targetFile}`, { cwd: tempDir }); + execSync('git commit -m "Initial commit"', { cwd: tempDir }); + + // Switch cwd to tempDir + const originalCwd = process.cwd(); + try { + process.chdir(tempDir); + console.log(`Changed cwd to: ${process.cwd()}`); + + // Import governance service dynamically so it picks up the new cwd + const governance = await import(governanceServicePath); + + // Malicious payload + const maliciousPayload = 'attacker"; touch /tmp/pwned_check; echo "'; + // const proposalId = `test-proposal-${Date.now()}`; // unused + + // Manually create proposal metadata and files because createProposal also uses git diff + // We can use createProposal from the service, but we need to pass the malicious agent name there. + // Let's try using createProposal first. + + const proposalContent = `Shared contract (required):\nRequired startup + artifacts + memory + issue capture\n\nNew Content`; + + console.log('Creating proposal...'); + const { id } = await governance.createProposal({ + agent: maliciousPayload, + target: targetFile, + newContent: proposalContent, + reason: 'Testing security' + }); + + console.log(`Proposal created with ID: ${id}`); + + // Verify metadata has the malicious author + const metaPath = path.join(tempDir, 'src/proposals', id, 'meta.json'); + const meta = JSON.parse(await fs.readFile(metaPath, 'utf8')); + if (meta.author !== maliciousPayload) { + throw new Error('Meta author does not match malicious payload'); + } + + // Apply proposal + console.log('Applying proposal...'); + await governance.applyProposal(id); + console.log('Proposal applied.'); + + // Verification 1: Check if /tmp/pwned_check exists + try { + await fs.access('/tmp/pwned_check'); + console.error('VULNERABILITY DETECTED: /tmp/pwned_check exists!'); + process.exit(1); + } catch { + console.log('Security check passed: /tmp/pwned_check does not exist.'); + } + + // Verification 2: Check git log for commit message + const log = execSync('git log -1 --pretty=%B', { cwd: tempDir }).toString(); + console.log(`Commit message: ${log}`); + + if (!log.includes(maliciousPayload)) { + console.error('Verification failed: Commit message does not contain the payload literally.'); + process.exit(1); + } + + // Check if the commit message looks "broken" (e.g. if newlines were injected) + // Here we just want to ensure the whole thing is in the message. + console.log('Verification passed: Commit message contains payload.'); + + } finally { + // Cleanup + process.chdir(originalCwd); + await fs.rm(tempDir, { recursive: true, force: true }); + // Also remove /tmp/pwned_check if it exists (it shouldn't) + try { await fs.unlink('/tmp/pwned_check'); } catch { /* ignore */ } + } +} + +runTest().catch(err => { + console.error('Test failed:', err); + process.exit(1); +}); diff --git a/test/governance-security.test.mjs b/test/governance-security.test.mjs new file mode 100644 index 0000000..cefa3c1 --- /dev/null +++ b/test/governance-security.test.mjs @@ -0,0 +1,25 @@ +import { test } from 'node:test'; +import { execFileSync } from 'node:child_process'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import assert from 'node:assert'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const implPath = path.join(__dirname, 'governance-security-impl.mjs'); + +test('Governance Service Security: Command Injection Prevention', (_t) => { + // We run the implementation in a separate process to ensure process.cwd() isolation + // and to verify that the dynamic import picks up the correct paths. + try { + const output = execFileSync(process.execPath, [implPath], { encoding: 'utf8' }); + // If we reach here, the process exited with code 0 (success) + // We can optionally check the output for specific messages if needed, + // but the implementation script asserts internally. + assert.ok(output.includes('Verification passed: Commit message contains payload.'), 'Output should confirm verification passed'); + } catch (err) { + console.error('Test implementation failed.'); + if (err.stdout) console.error('STDOUT:', err.stdout); + if (err.stderr) console.error('STDERR:', err.stderr); + assert.fail(`Security test failed with exit code ${err.status}: ${err.message}`); + } +}); diff --git a/test/lib.test.mjs b/test/lib.test.mjs index 050308e..04e10b8 100644 --- a/test/lib.test.mjs +++ b/test/lib.test.mjs @@ -96,6 +96,24 @@ describe('src/lib.mjs', () => { // unknown agents are not in roster, so available should be full roster (since no roster agent is locked) assert.deepStrictEqual(result.available, mockRoster.sort()); }); + + it('suppresses relay query loggers when quiet mode is enabled', async () => { + let receivedQueryDeps = null; + const deps = { + ...baseDeps, + json: true, + quiet: true, + queryLocks: async (_relays, _cadence, _dateStr, _namespace, queryDeps) => { + receivedQueryDeps = queryDeps; + return []; + }, + }; + + await cmdCheck('daily', deps); + + assert.equal(typeof receivedQueryDeps.errorLogger, 'function'); + assert.equal(typeof receivedQueryDeps.healthLogger, 'function'); + }); }); describe('cmdLock', () => { diff --git a/test/run-scheduler-cycle-memory-policy.test.mjs b/test/run-scheduler-cycle-memory-policy.test.mjs index b738e61..6af066b 100644 --- a/test/run-scheduler-cycle-memory-policy.test.mjs +++ b/test/run-scheduler-cycle-memory-policy.test.mjs @@ -59,6 +59,9 @@ async function setupFixture({ roster = ['agent-a'], lockHealthPreflight = undefined, schedulerPolicy = {}, + handoffCommand = 'echo HANDOFF_OK', + validationCommands = ['true'], + lockCheckOutput = `echo '{"excluded":[]}'`, preflightScript = '#!/usr/bin/env node\nconsole.log(JSON.stringify({ ok: true, relays: ["wss://relay.test"] }));\nprocess.exit(0);\n', }) { const root = await fs.mkdtemp(path.join(os.tmpdir(), 'scheduler-memory-policy-')); @@ -103,8 +106,8 @@ async function setupFixture({ JSON.stringify({ scheduler: { firstPromptByCadence: { daily: 'agent-a' }, - handoffCommandByCadence: { daily: 'echo HANDOFF_OK' }, - validationCommandsByCadence: { daily: ['true'] }, + handoffCommandByCadence: { daily: handoffCommand }, + validationCommandsByCadence: { daily: validationCommands }, memoryPolicyByCadence: { daily: memoryPolicy }, ...schedulerPolicy, ...(lockHealthPreflight === undefined ? {} : { lockHealthPreflight }), @@ -122,7 +125,7 @@ if [[ "$1" != "run" ]]; then fi case "$2" in lock:check:daily) - echo '{"excluded":[]}' +${lockCheckOutput} ;; lock:health) shift 2 @@ -611,3 +614,110 @@ test('does not use backend retry flow for exit code 3 lock conflicts', { concurr assert.equal(result.code, 0, `stdout: ${result.stdout}\nstderr: ${result.stderr}`); assert.doesNotMatch(result.stdout, /"event":"scheduler.lock.retry"/); }); + +test('retries handoff command for retryable network failures and succeeds', { concurrency: false }, async () => { + const fixture = await setupFixture({ + memoryPolicy: { mode: 'optional' }, + handoffCommand: `count_file="$PWD/.handoff-count" +count=0 +if [[ -f "$count_file" ]]; then + count="$(cat "$count_file")" +fi +count=$((count + 1)) +echo "$count" > "$count_file" +if [[ "$count" -lt 2 ]]; then + echo "stream disconnected before completion" >&2 + exit 12 +fi +echo HANDOFF_OK`, + schedulerPolicy: { + handoffPolicyByCadence: { + daily: { + maxAttempts: 3, + retryBackoffMs: 0, + }, + }, + }, + }); + + const result = await runNode(fixture.scriptPath, ['--cadence', 'daily'], { + cwd: fixture.root, + env: fixture.env, + }); + + assert.equal(result.code, 0, `stdout: ${result.stdout}\nstderr: ${result.stderr}`); + const logs = await fs.readdir(path.join(fixture.root, 'task-logs', 'daily')); + assert.ok(logs.some((name) => name.endsWith('__completed.md'))); +}); + +test('uses fallback platform for handoff when primary platform fails', { concurrency: false }, async () => { + const fixture = await setupFixture({ + memoryPolicy: { mode: 'optional' }, + handoffCommand: `if [[ "$AGENT_PLATFORM" == "linux" ]]; then + echo HANDOFF_FALLBACK_OK + exit 0 +fi +echo "stream disconnected before completion" >&2 +exit 13`, + schedulerPolicy: { + handoffPolicyByCadence: { + daily: { + maxAttempts: 1, + fallbackPlatform: 'linux', + }, + }, + }, + }); + + const result = await runNode(fixture.scriptPath, ['--cadence', 'daily', '--platform', 'codex'], { + cwd: fixture.root, + env: fixture.env, + }); + + assert.equal(result.code, 0, `stdout: ${result.stdout}\nstderr: ${result.stderr}`); + const logs = await fs.readdir(path.join(fixture.root, 'task-logs', 'daily')); + assert.ok(logs.some((name) => name.endsWith('__completed.md'))); +}); + +test('fails before lock acquisition when runner health preflight command fails', { concurrency: false }, async () => { + const fixture = await setupFixture({ + memoryPolicy: { mode: 'optional' }, + schedulerPolicy: { + runnerHealthCommandByCadence: { + daily: 'echo RUNNER_UNAVAILABLE >&2; exit 9', + }, + }, + }); + + const result = await runNode(fixture.scriptPath, ['--cadence', 'daily'], { + cwd: fixture.root, + env: fixture.env, + }); + + assert.equal(result.code, 9, `stdout: ${result.stdout}\nstderr: ${result.stderr}`); + const logs = await fs.readdir(path.join(fixture.root, 'task-logs', 'daily')); + const failedLog = logs.find((name) => name.endsWith('__failed.md')); + assert.ok(failedLog); + const failedBody = await fs.readFile(path.join(fixture.root, 'task-logs', 'daily', failedLog), 'utf8'); + assert.match(failedBody, /reason: Runner health preflight failed/); + assert.match(failedBody, /failure_class: 'runner_unavailable'/); + assert.match(failedBody, /failure_category: 'execution_error'/); + assert.match(failedBody, /Prompt not executed\./); +}); + +test('uses exclusion payload from mixed JSON lock:check output', { concurrency: false }, async () => { + const fixture = await setupFixture({ + memoryPolicy: { mode: 'optional' }, + roster: ['agent-a', 'agent-b'], + lockCheckOutput: `echo '{"event":"relay_health_snapshot"}' +echo '{"excluded":["agent-a"]}'`, + }); + + const result = await runNode(fixture.scriptPath, ['--cadence', 'daily'], { + cwd: fixture.root, + env: fixture.env, + }); + + assert.equal(result.code, 0, `stdout: ${result.stdout}\nstderr: ${result.stderr}`); + assert.match(result.stdout, /Agent:\s+agent-b/); +}); diff --git a/test/test-audit-run-flaky-check.test.mjs b/test/test-audit-run-flaky-check.test.mjs new file mode 100644 index 0000000..73fceaf --- /dev/null +++ b/test/test-audit-run-flaky-check.test.mjs @@ -0,0 +1,45 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { spawn } from 'node:child_process'; + +const SCRIPT = path.resolve('scripts/test-audit/run-flaky-check.mjs'); + +function runFlakyCheck(args) { + return new Promise((resolve) => { + const childEnv = { ...process.env }; + delete childEnv.NODE_TEST_CONTEXT; + delete childEnv.NODE_UNIQUE_ID; + + const child = spawn(process.execPath, [SCRIPT, ...args], { + cwd: process.cwd(), + stdio: ['ignore', 'pipe', 'pipe'], + env: childEnv, + }); + + let stdout = ''; + let stderr = ''; + child.stdout.on('data', (chunk) => { stdout += chunk.toString(); }); + child.stderr.on('data', (chunk) => { stderr += chunk.toString(); }); + child.on('close', (code) => resolve({ code: code ?? 1, stdout, stderr })); + }); +} + +test('run-flaky-check reports pass/fail counts from TAP output files', async () => { + const outputDir = await fs.mkdtemp(path.join(os.tmpdir(), 'flaky-check-report-')); + const passFixture = 'test/fixtures/flaky-check/pass.fixture.test.mjs'; + const failFixture = 'test/fixtures/flaky-check/fail.fixture.test.mjs'; + + const result = await runFlakyCheck(['--output-dir', outputDir, passFixture, failFixture]); + assert.equal(result.code, 0, result.stderr || result.stdout); + + const matrix = JSON.parse(await fs.readFile(path.join(outputDir, 'flakiness-matrix.json'), 'utf8')); + assert.deepEqual(matrix[passFixture], { pass: 5, fail: 0 }); + assert.deepEqual(matrix[failFixture], { pass: 0, fail: 5 }); + + const diagnostics = JSON.parse(await fs.readFile(path.join(outputDir, 'flakiness-runs.json'), 'utf8')); + assert.equal(diagnostics.length, 5); + assert.ok(diagnostics.every((run) => run.observed >= 2)); +}); diff --git a/test/utils.test.mjs b/test/utils.test.mjs index 40db583..da7213a 100644 --- a/test/utils.test.mjs +++ b/test/utils.test.mjs @@ -2,7 +2,8 @@ import { describe, it, after } from 'node:test'; import assert from 'node:assert'; import fs from 'node:fs'; import path from 'node:path'; -import { getIsoWeekStr, todayDateStr, nowUnix, ensureDir, detectPlatform, relayListLabel, mergeRelayList } from '../src/utils.mjs'; +import { getIsoWeekStr, todayDateStr, nowUnix, ensureDir, detectPlatform, relayListLabel } from '../src/utils.mjs'; +import { mergeRelayList } from '../src/lock-utils.mjs'; describe('Date Utilities', () => { describe('todayDateStr', () => { diff --git a/test/verify-example-config-security.test.mjs b/test/verify-example-config-security.test.mjs deleted file mode 100644 index fffd5b2..0000000 --- a/test/verify-example-config-security.test.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import test from 'node:test'; -import assert from 'node:assert'; -import fs from 'node:fs'; -import path from 'node:path'; - -test('torch-config.example.json Security', () => { - const configPath = path.resolve('torch-config.example.json'); - const content = fs.readFileSync(configPath, 'utf8'); - const config = JSON.parse(content); - - if (config.dashboard) { - assert.strictEqual(config.dashboard.auth, undefined, 'Example config should not contain auth field'); - } -}); diff --git a/test_output.txt b/test_output.txt deleted file mode 100644 index bd57713..0000000 --- a/test_output.txt +++ /dev/null @@ -1,3630 +0,0 @@ - -> torch-lock@0.1.0 test -> npm run validate:scheduler && npm run test:integration:e2e && node --test --test-timeout=30000 test/*.test.mjs test/*.test.js - - -> torch-lock@0.1.0 validate:scheduler -> node scripts/validate-scheduler-roster.mjs && node scripts/validate-prompt-contract.mjs && node scripts/validate-scheduler-flow-parity.mjs && npm run validate:scheduler-failure-schema - -Scheduler tables, roster, and prompt filenames are in sync. -Prompt contract validated for all daily/weekly prompts. -Scheduler flow parity validated for prompts and scheduler implementation. - -> torch-lock@0.1.0 validate:scheduler-failure-schema -> node --test test/scheduler-lock-failure-schema.contract.test.mjs - -TAP version 13 -# Subtest: scheduler lock backend failure artifact matches required frontmatter schema -ok 1 - scheduler lock backend failure artifact matches required frontmatter schema - --- - duration_ms: 117.371092 - type: 'test' - ... -# Subtest: schema contract catches missing required lock-backend field -ok 2 - schema contract catches missing required lock-backend field - --- - duration_ms: 1.918327 - type: 'test' - ... -# Subtest: schema contract catches misnamed lock-backend key -ok 3 - schema contract catches misnamed lock-backend key - --- - duration_ms: 0.581289 - type: 'test' - ... -1..3 -# tests 3 -# suites 0 -# pass 3 -# fail 0 -# cancelled 0 -# skipped 0 -# todo 0 -# duration_ms 207.28964 - -> torch-lock@0.1.0 test:integration:e2e -> node --test test/scheduler-preflight-lock.e2e.test.mjs - -TAP version 13 -# Subtest: lock preflight e2e: successful lock writes completed status snapshot -ok 1 - lock preflight e2e: successful lock writes completed status snapshot - --- - duration_ms: 414.630633 - type: 'test' - ... -# Subtest: lock preflight e2e: exit code 2 quorum failure persists failed backend status and prompt-not-started marker -ok 2 - lock preflight e2e: exit code 2 quorum failure persists failed backend status and prompt-not-started marker - --- - duration_ms: 97.529266 - type: 'test' - ... -# Subtest: lock preflight e2e: non-lock failure exits failed without prompt parse/schema classification -ok 3 - lock preflight e2e: non-lock failure exits failed without prompt parse/schema classification - --- - duration_ms: 99.62337 - type: 'test' - ... -1..3 -# tests 3 -# suites 0 -# pass 3 -# fail 0 -# cancelled 0 -# skipped 0 -# todo 0 -# duration_ms 698.581135 -TAP version 13 -# Subtest: Build artifacts verification -ok 1 - Build artifacts verification # SKIP dist/ not found — run npm run build first - --- - duration_ms: 1.053477 - type: 'test' - ... -# Subtest: parseArgs should parse basic command -ok 2 - parseArgs should parse basic command - --- - duration_ms: 1.920765 - type: 'test' - ... -# Subtest: parseArgs should parse agent and cadence -ok 3 - parseArgs should parse agent and cadence - --- - duration_ms: 0.292371 - type: 'test' - ... -# Subtest: parseArgs should parse equals sign syntax -ok 4 - parseArgs should parse equals sign syntax - --- - duration_ms: 2.036165 - type: 'test' - ... -# Subtest: parseArgs should parse dry-run flag -ok 5 - parseArgs should parse dry-run flag - --- - duration_ms: 0.484127 - type: 'test' - ... -# Subtest: parseArgs should parse port with default -ok 6 - parseArgs should parse port with default - --- - duration_ms: 0.237153 - type: 'test' - ... -# Subtest: parseArgs should parse custom port -ok 7 - parseArgs should parse custom port - --- - duration_ms: 0.186289 - type: 'test' - ... -# Subtest: parseArgs should parse ignore-logs flag -ok 8 - parseArgs should parse ignore-logs flag - --- - duration_ms: 0.315157 - type: 'test' - ... -# Subtest: parseArgs should parse memory flags -ok 9 - parseArgs should parse memory flags - --- - duration_ms: 1.156303 - type: 'test' - ... -# Subtest: parseArgs should parse memory flags with equals -ok 10 - parseArgs should parse memory flags with equals - --- - duration_ms: 0.579379 - type: 'test' - ... -# Subtest: cmdList - # Subtest: should list active locks and identify unknown agents - ok 1 - should list active locks and identify unknown agents - --- - duration_ms: 3.93891 - type: 'test' - ... - 1..1 -ok 11 - cmdList - --- - duration_ms: 5.163538 - type: 'suite' - ... -# Subtest: cmdRollback - # Subtest: should throw ExitError if target is missing - ok 1 - should throw ExitError if target is missing - --- - duration_ms: 1.901957 - type: 'test' - ... - # Subtest: should list versions when list option is true - ok 2 - should list versions when list option is true - --- - duration_ms: 1.176004 - type: 'test' - ... - # Subtest: should throw ExitError if list versions fails - ok 3 - should throw ExitError if list versions fails - --- - duration_ms: 0.420189 - type: 'test' - ... - # Subtest: should rollback to latest if strategy is missing - ok 4 - should rollback to latest if strategy is missing - --- - duration_ms: 0.322401 - type: 'test' - ... - # Subtest: should rollback to specific strategy - ok 5 - should rollback to specific strategy - --- - duration_ms: 0.309532 - type: 'test' - ... - # Subtest: should throw ExitError if rollback fails - ok 6 - should throw ExitError if rollback fails - --- - duration_ms: 0.354113 - type: 'test' - ... - 1..6 -ok 12 - cmdRollback - --- - duration_ms: 6.271138 - type: 'suite' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Authentication: enabled (Basic Auth) -# Serving files from /app -# Using configuration from /app -# Subtest: Dashboard Authentication - # Subtest: returns 401 when auth is required but missing - ok 1 - returns 401 when auth is required but missing - --- - duration_ms: 35.702763 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Authentication: enabled (Basic Auth) -# Serving files from /app -# Using configuration from /app - # Subtest: returns 401 when malformed auth header is provided - ok 2 - returns 401 when malformed auth header is provided - --- - duration_ms: 11.118495 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Authentication: enabled (Basic Auth) -# Serving files from /app -# Using configuration from /app - # Subtest: returns 200 when valid credentials are provided - ok 3 - returns 200 when valid credentials are provided - --- - duration_ms: 9.470676 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Authentication: enabled (Basic Auth) -# Serving files from /app -# Using configuration from /app - # Subtest: returns 401 when password has different byte length than stored credential - ok 4 - returns 401 when password has different byte length than stored credential - --- - duration_ms: 16.398848 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app -# Authentication: DISABLED (Dashboard is public) - # Subtest: returns 200 when auth is disabled - ok 5 - returns 200 when auth is disabled - --- - duration_ms: 6.015668 - type: 'test' - ... - 1..5 -ok 13 - Dashboard Authentication - --- - duration_ms: 82.1868 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Authentication: DISABLED (Dashboard is public) -# Serving files from /app -# Using configuration from /app -# Subtest: Dashboard Configuration Exposure Security -ok 14 - Dashboard Configuration Exposure Security - --- - duration_ms: 40.458613 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Authentication: DISABLED (Dashboard is public) -# Serving files from /app -# Using configuration from /app -# Subtest: Dashboard Configuration Security - # Subtest: sanitizes config when auth is disabled - ok 1 - sanitizes config when auth is disabled - --- - duration_ms: 34.019617 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Authentication: enabled (Basic Auth) -# Serving files from /app -# Using configuration from /app - # Subtest: sanitizes auth credentials even when authenticated - ok 2 - sanitizes auth credentials even when authenticated - --- - duration_ms: 12.289752 - type: 'test' - ... - 1..2 -ok 15 - Dashboard Configuration Security - --- - duration_ms: 48.899054 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Authentication: DISABLED (Dashboard is public) -# Serving files from /app -# Using configuration from /app -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Authentication: DISABLED (Dashboard is public) -# Serving files from /app -# Using configuration from /app -# Subtest: Dashboard Path Traversal Security Fix - # Subtest: allows access to valid dashboard index - ok 1 - allows access to valid dashboard index - --- - duration_ms: 39.332349 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app -# Authentication: DISABLED (Dashboard is public) - # Subtest: blocks traversal attempt: /../../etc/passwd - ok 2 - blocks traversal attempt: /../../etc/passwd - --- - duration_ms: 10.133948 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app -# Authentication: DISABLED (Dashboard is public) - # Subtest: blocks traversal attempt: /dashboard/../../etc/passwd - ok 3 - blocks traversal attempt: /dashboard/../../etc/passwd - --- - duration_ms: 5.850816 - type: 'test' - ... -# Authentication: DISABLED (Dashboard is public) -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app - # Subtest: blocks traversal attempt: /dashboard/../../../etc/passwd - ok 4 - blocks traversal attempt: /dashboard/../../../etc/passwd - --- - duration_ms: 5.715213 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app -# Authentication: DISABLED (Dashboard is public) - # Subtest: blocks traversal attempt: /../package.json - ok 5 - blocks traversal attempt: /../package.json - --- - duration_ms: 7.108694 - type: 'test' - ... -# Authentication: DISABLED (Dashboard is public) -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app - # Subtest: blocks traversal attempt: /dashboard/../package.json - ok 6 - blocks traversal attempt: /dashboard/../package.json - --- - duration_ms: 3.369636 - type: 'test' - ... -# Authentication: DISABLED (Dashboard is public) -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app - # Subtest: blocks traversal attempt: /dashboard/%2e%2e/package.json - ok 7 - blocks traversal attempt: /dashboard/%2e%2e/package.json - --- - duration_ms: 4.409576 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app -# Authentication: DISABLED (Dashboard is public) - # Subtest: blocks traversal attempt: /dashboard/%2e%2e%2fpackage.json - ok 8 - blocks traversal attempt: /dashboard/%2e%2e%2fpackage.json - --- - duration_ms: 4.061852 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app -# Authentication: DISABLED (Dashboard is public) - # Subtest: blocks traversal attempt: /..%2fpackage.json - ok 9 - blocks traversal attempt: /..%2fpackage.json - --- - duration_ms: 7.298358 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app -# Authentication: DISABLED (Dashboard is public) - # Subtest: blocks traversal attempt: /%2e%2e%2fpackage.json - ok 10 - blocks traversal attempt: /%2e%2e%2fpackage.json - --- - duration_ms: 4.69589 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app -# Authentication: DISABLED (Dashboard is public) - # Subtest: blocks traversal attempt: /dashboard/..%5cpackage.json - ok 11 - blocks traversal attempt: /dashboard/..%5cpackage.json - --- - duration_ms: 4.323659 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Authentication: DISABLED (Dashboard is public) -# Serving files from /app -# Using configuration from /app - # Subtest: blocks traversal attempt: /dashboard/..%252fpackage.json - ok 12 - blocks traversal attempt: /dashboard/..%252fpackage.json - --- - duration_ms: 7.718419 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app -# Authentication: DISABLED (Dashboard is public) - # Subtest: blocks traversal attempt: /dashboard/....//package.json - ok 13 - blocks traversal attempt: /dashboard/....//package.json - --- - duration_ms: 4.320502 - type: 'test' - ... -# Authentication: DISABLED (Dashboard is public) -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app - # Subtest: blocks traversal attempt: /dashboard/.../package.json - ok 14 - blocks traversal attempt: /dashboard/.../package.json - --- - duration_ms: 3.412551 - type: 'test' - ... -# Authentication: DISABLED (Dashboard is public) -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app - # Subtest: blocks traversal attempt: //etc/passwd - ok 15 - blocks traversal attempt: //etc/passwd - --- - duration_ms: 3.466761 - type: 'test' - ... -# Authentication: DISABLED (Dashboard is public) -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app - # Subtest: blocks traversal attempt: /dashboard//../../etc/passwd - ok 16 - blocks traversal attempt: /dashboard//../../etc/passwd - --- - duration_ms: 3.881708 - type: 'test' - ... - # Subtest: allows traversal within allowed paths - ok 17 - allows traversal within allowed paths - --- - duration_ms: 4.074839 - type: 'test' - ... - 1..17 -ok 16 - Dashboard Path Traversal Security Fix - --- - duration_ms: 130.148627 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Authentication: DISABLED (Dashboard is public) -# Serving files from /app -# Using configuration from /app -# Authentication: DISABLED (Dashboard is public) -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app -# Subtest: Dashboard File Access Security - # Subtest: allows access to dashboard assets - ok 1 - allows access to dashboard assets - --- - duration_ms: 33.056461 - type: 'test' - ... -# Authentication: DISABLED (Dashboard is public) -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app - # Subtest: allows access to global assets - ok 2 - allows access to global assets - --- - duration_ms: 10.881727 - type: 'test' - ... -# Authentication: DISABLED (Dashboard is public) -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app - # Subtest: allows access to landing page - ok 3 - allows access to landing page - --- - duration_ms: 8.14291 - type: 'test' - ... -# Authentication: DISABLED (Dashboard is public) -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app - # Subtest: blocks access to package.json - ok 4 - blocks access to package.json - --- - duration_ms: 6.995424 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app -# Authentication: DISABLED (Dashboard is public) - # Subtest: blocks access to src/dashboard.mjs - ok 5 - blocks access to src/dashboard.mjs - --- - duration_ms: 8.143755 - type: 'test' - ... -# Authentication: DISABLED (Dashboard is public) -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app - # Subtest: blocks access to random root files - ok 6 - blocks access to random root files - --- - duration_ms: 4.275454 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app -# Authentication: DISABLED (Dashboard is public) - # Subtest: allows access to torch-config.json via special handler - ok 7 - allows access to torch-config.json via special handler - --- - duration_ms: 4.782726 - type: 'test' - ... -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Authentication: DISABLED (Dashboard is public) -# Serving files from /app -# Using configuration from /app - # Subtest: blocks directory traversal attempts - ok 8 - blocks directory traversal attempts - --- - duration_ms: 4.775291 - type: 'test' - ... -# Authentication: DISABLED (Dashboard is public) -# Dashboard running at http://127.0.0.1:0/dashboard/ -# Serving files from /app -# Using configuration from /app - # Subtest: responses include security headers - ok 9 - responses include security headers - --- - duration_ms: 8.161888 - type: 'test' - ... - # Subtest: error responses include security headers - ok 10 - error responses include security headers - --- - duration_ms: 8.451116 - type: 'test' - ... - 1..10 -ok 17 - Dashboard File Access Security - --- - duration_ms: 102.497455 - type: 'test' - ... -# Subtest: ExitError - # Subtest: should capture the code and message correctly - ok 1 - should capture the code and message correctly - --- - duration_ms: 1.011124 - type: 'test' - ... - # Subtest: should be an instance of Error and ExitError - ok 2 - should be an instance of Error and ExitError - --- - duration_ms: 0.331021 - type: 'test' - ... - # Subtest: should have a stack trace - ok 3 - should have a stack trace - --- - duration_ms: 0.430355 - type: 'test' - ... - # Subtest: should have the correct name - ok 4 - should have the correct name - --- - duration_ms: 0.214278 - type: 'test' - ... - 1..4 -ok 18 - ExitError - --- - duration_ms: 3.702117 - type: 'suite' - ... -# Subtest: createProposal should throw error for disallowed target -ok 19 - createProposal should throw error for disallowed target - --- - duration_ms: 8.646563 - type: 'test' - ... -# Subtest: validateProposal should return invalid for disallowed target -ok 20 - validateProposal should return invalid for disallowed target - --- - duration_ms: 10.263447 - type: 'test' - ... -# Subtest: validateProposal should return valid for allowed target -ok 21 - validateProposal should return valid for allowed target - --- - duration_ms: 6.522006 - type: 'test' - ... -# Subtest: SCN-proposal-create: createProposal writes meta.json, new.md, change.diff -ok 22 - SCN-proposal-create: createProposal writes meta.json, new.md, change.diff - --- - duration_ms: 27.092798 - type: 'test' - ... -# Subtest: SCN-proposal-list: listProposals returns created proposal; status filter works -ok 23 - SCN-proposal-list: listProposals returns created proposal; status filter works - --- - duration_ms: 3.865235 - type: 'test' - ... -# Subtest: SCN-proposal-reject: rejectProposal marks proposal with rejected status and reason -ok 24 - SCN-proposal-reject: rejectProposal marks proposal with rejected status and reason - --- - duration_ms: 24.49883 - type: 'test' - ... -# Git commit failed (ignoring): Command failed: git commit -m feat(prompts): apply proposal 2026-02-21T18-29-49-232Z___test-agent__ by __test-agent__ -# Subtest: SCN-archive-naming: applyProposal archives with timestamp_hash filename and sidecar -ok 25 - SCN-archive-naming: applyProposal archives with timestamp_hash filename and sidecar - --- - duration_ms: 50.113982 - type: 'test' - ... -# Subtest: SCN-version-list: listPromptVersions returns versions newest-first with metadata -ok 26 - SCN-version-list: listPromptVersions returns versions newest-first with metadata - --- - duration_ms: 18.919596 - type: 'test' - ... -# Subtest: SCN-rollback-latest: rollbackPrompt restores the most recently archived content -ok 27 - SCN-rollback-latest: rollbackPrompt restores the most recently archived content - --- - duration_ms: 9.949048 - type: 'test' - ... -# Subtest: SCN-rollback-hash: rollbackPrompt --strategy <fragment> restores matching archive -ok 28 - SCN-rollback-hash: rollbackPrompt --strategy <fragment> restores matching archive - --- - duration_ms: 9.104187 - type: 'test' - ... -# Initializing torch in /tmp/torch-test-zAoiIZ/valid-dir_123... -# Copying application directories... -# Copied src/ -# Copied bin/ -# Copied dashboard/ -# Copied landing/ -# Copied assets/ -# Copied scripts/ -# Copying application files... -# Copied package.json -# Copied build.mjs -# Copied README.md -# Copied torch-config.example.json -# Copied TORCH.md -# Created valid-dir_123/roster.json -# Created valid-dir_123/META_PROMPTS.md -# Created valid-dir_123/scheduler-flow.md -# Created valid-dir_123/daily-scheduler.md -# Created valid-dir_123/weekly-scheduler.md -# Created 23 files in valid-dir_123/prompts/daily/ -# Created 21 files in valid-dir_123/prompts/weekly/ -# Created ../tmp/torch-test-zAoiIZ/valid-dir_123/.gitignore with node_modules -# Saved configuration to torch-config.json -# Created valid-dir_123/TORCH_DASHBOARD.md -# Added script: "torch:dashboard" -# Added script: "torch:check" -# Added script: "torch:lock" -# Added script: "torch:health" -# Added script: "torch:memory:list" -# Added script: "torch:memory:inspect" -# Updated package.json with convenience scripts. -# Initialization complete. -# You can now customize the files in valid-dir_123/ -# ● From torch-config.json: -# - Hashtag: ns-agent-lock -# - Namespace: ns -# https://torch.thepr0m3th3an.net/dashboard/?hashtag=ns-agent-lock&namespace=ns&relays= -# Initializing torch in /tmp/torch-test-4mRKGl... -# Copying application directories... -# Copied src/ -# Copied bin/ -# Copied dashboard/ -# Copied landing/ -# Copied assets/ -# Copied scripts/ -# Copying application files... -# Skipping package.json to avoid overwriting host package.json (installing to root). -# Copied build.mjs -# Copied README.md -# Copied torch-config.example.json -# Copied TORCH.md -# Created roster.json -# Created META_PROMPTS.md -# Created scheduler-flow.md -# Created daily-scheduler.md -# Created weekly-scheduler.md -# Created 23 files in prompts/daily/ -# Created 21 files in prompts/weekly/ -# Created ../tmp/torch-test-4mRKGl/.gitignore with node_modules -# Saved configuration to torch-config.json -# Created TORCH_DASHBOARD.md -# Initialization complete. -# You can now customize the files in / -# ● From torch-config.json: -# - Hashtag: ns-agent-lock -# - Namespace: ns -# https://torch.thepr0m3th3an.net/dashboard/?hashtag=ns-agent-lock&namespace=ns&relays= -# Initializing torch in /tmp/torch-test-iQ5e0z/path/to/torch... -# Copying application directories... -# Copied src/ -# Copied bin/ -# Copied dashboard/ -# Copied landing/ -# Copied assets/ -# Copied scripts/ -# Copying application files... -# Copied package.json -# Copied build.mjs -# Copied README.md -# Copied torch-config.example.json -# Copied TORCH.md -# Created path/to/torch/roster.json -# Created path/to/torch/META_PROMPTS.md -# Created path/to/torch/scheduler-flow.md -# Created path/to/torch/daily-scheduler.md -# Created path/to/torch/weekly-scheduler.md -# Created 23 files in path/to/torch/prompts/daily/ -# Created 21 files in path/to/torch/prompts/weekly/ -# Created ../tmp/torch-test-iQ5e0z/path/to/torch/.gitignore with node_modules -# Saved configuration to torch-config.json -# Created path/to/torch/TORCH_DASHBOARD.md -# Added script: "torch:dashboard" -# Added script: "torch:check" -# Added script: "torch:lock" -# Added script: "torch:health" -# Added script: "torch:memory:list" -# Added script: "torch:memory:inspect" -# Updated package.json with convenience scripts. -# Initialization complete. -# You can now customize the files in path/to/torch/ -# ● From torch-config.json: -# - Hashtag: ns-agent-lock -# - Namespace: ns -# https://torch.thepr0m3th3an.net/dashboard/?hashtag=ns-agent-lock&namespace=ns&relays= -# Subtest: cmdInit should validate install directory name - # Subtest: should reject directory with semicolon - ok 1 - should reject directory with semicolon - --- - duration_ms: 4.505777 - type: 'test' - ... - # Subtest: should reject directory with spaces - ok 2 - should reject directory with spaces - --- - duration_ms: 1.337062 - type: 'test' - ... - # Subtest: should reject directory with quotes - ok 3 - should reject directory with quotes - --- - duration_ms: 1.264902 - type: 'test' - ... - # Subtest: should reject directory with backticks - ok 4 - should reject directory with backticks - --- - duration_ms: 1.099303 - type: 'test' - ... - # Subtest: should reject directory with $ - ok 5 - should reject directory with $ - --- - duration_ms: 1.228449 - type: 'test' - ... - # Subtest: should accept valid directory names - ok 6 - should accept valid directory names - --- - duration_ms: 79.167156 - type: 'test' - ... - # Subtest: should accept "." - ok 7 - should accept "." - --- - duration_ms: 61.751396 - type: 'test' - ... - # Subtest: should accept nested paths with slashes - ok 8 - should accept nested paths with slashes - --- - duration_ms: 58.801989 - type: 'test' - ... - 1..8 -ok 29 - cmdInit should validate install directory name - --- - duration_ms: 213.64342 - type: 'test' - ... -# Subtest: src/lib.mjs - # Subtest: cmdCheck - # Subtest: returns correct structure with empty locks - ok 1 - returns correct structure with empty locks - --- - duration_ms: 6.543833 - type: 'test' - ... - # Subtest: identifies locked agents - ok 2 - identifies locked agents - --- - duration_ms: 0.618197 - type: 'test' - ... - # Subtest: excludes paused agents - ok 3 - excludes paused agents - --- - duration_ms: 0.670254 - type: 'test' - ... - # Subtest: identifies unknown locked agents - ok 4 - identifies unknown locked agents - --- - duration_ms: 0.970025 - type: 'test' - ... - 1..4 - ok 1 - cmdCheck - --- - duration_ms: 10.268212 - type: 'suite' - ... - # Subtest: cmdLock - # Subtest: successfully locks an available agent - ok 1 - successfully locks an available agent - --- - duration_ms: 29.932387 - type: 'test' - ... - # Subtest: fails if agent is not in roster - ok 2 - fails if agent is not in roster - --- - duration_ms: 12.17838 - type: 'test' - ... - # Subtest: fails if agent is already locked - ok 3 - fails if agent is already locked - --- - duration_ms: 9.105251 - type: 'test' - ... - # Subtest: fails if agent is already completed - ok 4 - fails if agent is already completed - --- - duration_ms: 9.656007 - type: 'test' - ... - # Subtest: fails if race check is lost - ok 5 - fails if race check is lost - --- - duration_ms: 12.698185 - type: 'test' - ... - # Subtest: handles race condition with identical timestamps using eventId tie-breaker (we win) - ok 6 - handles race condition with identical timestamps using eventId tie-breaker (we win) - --- - duration_ms: 26.298913 - type: 'test' - ... - # Subtest: handles race condition with identical timestamps using eventId tie-breaker (we lose) - ok 7 - handles race condition with identical timestamps using eventId tie-breaker (we lose) - --- - duration_ms: 10.503332 - type: 'test' - ... - # Subtest: dry run does not publish - ok 8 - dry run does not publish - --- - duration_ms: 10.754629 - type: 'test' - ... - 1..8 - ok 2 - cmdLock - --- - duration_ms: 122.689768 - type: 'suite' - ... - # Subtest: cmdList - # Subtest: lists active locks - ok 1 - lists active locks - --- - duration_ms: 1.129469 - type: 'test' - ... - # Subtest: warns about unknown agents - ok 2 - warns about unknown agents - --- - duration_ms: 0.419319 - type: 'test' - ... - # Subtest: handles no locks - ok 3 - handles no locks - --- - duration_ms: 0.353545 - type: 'test' - ... - 1..3 - ok 3 - cmdList - --- - duration_ms: 2.191077 - type: 'suite' - ... - # Subtest: cmdComplete - # Subtest: successfully completes an active lock - ok 1 - successfully completes an active lock - --- - duration_ms: 1.008336 - type: 'test' - ... - # Subtest: fails if no active lock found - ok 2 - fails if no active lock found - --- - duration_ms: 0.48413 - type: 'test' - ... - # Subtest: detects already completed task - ok 3 - detects already completed task - --- - duration_ms: 0.401106 - type: 'test' - ... - 1..3 - ok 4 - cmdComplete - --- - duration_ms: 2.166842 - type: 'suite' - ... - 1..4 -ok 30 - src/lib.mjs - --- - duration_ms: 138.309258 - type: 'suite' - ... -# [publish:primary] Publishing to 3 relays (wss://relay-a, wss://relay-b, wss://relay-c)... -# [publish:primary] Publishing to 3 relays (wss://relay-a, wss://relay-b, wss://relay-c)... -# [publish:primary] Publishing to 3 relays (wss://relay-a, wss://relay-b, wss://relay-c)... -# [publish:primary] Publishing to 3 relays (wss://relay-a, wss://relay-b, wss://relay-c)... -# Published to 1/3 relays (required=1, timeout=25ms) -# [publish:primary] Publishing to 2 relays (wss://relay-a, wss://relay-b)... -# [publish:primary] Publishing to 2 relays (wss://relay-a, wss://relay-b)... -# Published to 2/2 relays (required=2, timeout=25ms) -# [publish:primary] Publishing to 3 relays (wss://relay-a, wss://relay-b, wss://relay-c)... -# [publish:primary] Publishing to 3 relays (wss://relay-a, wss://relay-b, wss://relay-c)... -# [publish:primary] Publishing to 3 relays (wss://relay-a, wss://relay-b, wss://relay-c)... -# [publish:primary] Publishing to 3 relays (wss://relay-a, wss://relay-b, wss://relay-c)... -# Published to 2/3 relays (required=2, timeout=25ms) -# (node:5597) ExperimentalWarning: The MockTimers API is an experimental feature and might change at any time -# (Use `node --trace-warnings ...` to show where the warning was created) -# Subtest: ci-resilience lock acquisition under unstable relays - # Subtest: recovers after all relays are down for an initial outage window - ok 1 - recovers after all relays are down for an initial outage window - --- - duration_ms: 26.515959 - type: 'test' - ... - # Subtest: survives intermittent timeout spikes and reaches quorum with bounded retries - ok 2 - survives intermittent timeout spikes and reaches quorum with bounded retries - --- - duration_ms: 3.799549 - type: 'test' - ... - # Subtest: handles staggered relay recovery and never exceeds retry budget - ok 3 - handles staggered relay recovery and never exceeds retry budget - --- - duration_ms: 5.563341 - type: 'test' - ... - 1..3 -ok 31 - ci-resilience lock acquisition under unstable relays - --- - duration_ms: 37.800235 - type: 'suite' - ... -# [publish:primary] Publishing to 1 relays (wss://relay-a)... -# Published to 1/1 relays (required=1, timeout=200ms) -# [publish:primary] Publishing to 1 relays (wss://relay-a)... -# [publish:primary] Publishing to 1 relays (wss://relay-a)... -# Published to 1/1 relays (required=1, timeout=200ms) -# [publish:primary] Publishing to 1 relays (wss://relay-a)... -# [publish:primary] Publishing to 1 relays (wss://relay-a)... -# [publish:primary] Publishing to 1 relays (wss://relay-a)... -# [publish:primary] Publishing to 1 relays (wss://relay-a)... -# (node:5616) ExperimentalWarning: The MockTimers API is an experimental feature and might change at any time -# (Use `node --trace-warnings ...` to show where the warning was created) -# Subtest: publishLock retry behavior - # Subtest: succeeds immediately on first attempt without retries - ok 1 - succeeds immediately on first attempt without retries - --- - duration_ms: 7.267719 - type: 'test' - ... - # Subtest: retries a transient failure then succeeds within retry budget with bounded jitter - ok 2 - retries a transient failure then succeeds within retry budget with bounded jitter - --- - duration_ms: 3.922625 - type: 'test' - ... - # Subtest: returns terminal relay_publish_quorum_failure after exhausting retry budget - ok 3 - returns terminal relay_publish_quorum_failure after exhausting retry budget - --- - duration_ms: 4.106371 - type: 'test' - ... - # Subtest: short-circuits non-retryable failures without extra attempts - ok 4 - short-circuits non-retryable failures without extra attempts - --- - duration_ms: 2.134059 - type: 'test' - ... - 1..4 -ok 32 - publishLock retry behavior - --- - duration_ms: 19.464757 - type: 'suite' - ... -# Subtest: lock-ops - # Subtest: parseLockEvent - # Subtest: parses a valid lock event correctly - ok 1 - parses a valid lock event correctly - --- - duration_ms: 1.918056 - type: 'test' - ... - # Subtest: returns nulls when content is invalid JSON - ok 2 - returns nulls when content is invalid JSON - --- - duration_ms: 0.580037 - type: 'test' - ... - # Subtest: returns nulls when required content fields are missing - ok 3 - returns nulls when required content fields are missing - --- - duration_ms: 0.299477 - type: 'test' - ... - # Subtest: returns nulls when content is an array (not object) - ok 4 - returns nulls when content is an array (not object) - --- - duration_ms: 1.073437 - type: 'test' - ... - 1..4 - ok 1 - parseLockEvent - --- - duration_ms: 5.283597 - type: 'suite' - ... -# [publish:primary] Publishing to 2 relays (wss://primary-a, wss://primary-b)... -# [publish:primary] Publishing to 2 relays (wss://primary-a, wss://primary-b)... -# Published to 2/2 relays (required=1, timeout=200ms) -# [publish:primary] Publishing to 1 relays (wss://primary-down)... -# [publish:primary] Publishing to 1 relays (wss://primary-down)... -# [publish:primary] Publishing to 1 relays (wss://primary-down)... -# [publish:primary] Publishing to 1 relays (wss://primary-bad)... -# [publish:primary] Publishing to 5 relays (relay-timeout, wss://dns, wss://tcp, wss://tls, wss://ws)... -# [publish:primary] Publishing to 2 relays (wss://relay-bad, wss://relay-ok)... -# {"event":"lock_publish_quorum_met","correlationId":"04a54eb6-6a07-4929-9daa-4bec5be2f674","attemptId":"693ce3bd-ee91-4e4e-856f-bf26a2e20e27","publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"elapsedMs":1}],"totalElapsedMs":1} -# Published to 1/2 relays (required=1, timeout=200ms) -# [publish:primary] Publishing to 1 relays (wss://primary-bad)... -# [publish:fallback] Publishing to 1 relays (wss://fallback-ok)... -# {"event":"lock_publish_quorum_met","correlationId":"308e8d10-a303-490e-9867-e4be79cdb52a","attemptId":"bc51f49b-4016-4194-9189-cbca829872e3","publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"elapsedMs":0}],"totalElapsedMs":0} -# Published to 1/2 relays (required=1, timeout=200ms) -# [publish:primary] Publishing to 2 relays (wss://bad, wss://healthy)... -# {"event":"lock_publish_quorum_met","correlationId":"f8bc5969-f56d-4548-979d-575c7fb8329a","attemptId":"a4e5aa0f-ec09-4d6f-b134-7d800f8ad024","publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"elapsedMs":0}],"totalElapsedMs":0} -# Published to 1/2 relays (required=1, timeout=200ms) -# [publish:primary] Publishing to 2 relays (wss://healthy, wss://bad)... -# {"event":"lock_publish_quorum_met","correlationId":"e17e7d25-938b-45b5-9590-78308bf61248","attemptId":"78781b04-3ec0-465f-b9b1-8d64112aefb0","publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"elapsedMs":0}],"totalElapsedMs":0} -# Published to 1/2 relays (required=1, timeout=200ms) -# [publish:primary] Publishing to 1 relays (wss://healthy)... -# {"event":"lock_publish_quorum_met","correlationId":"ccb37d4d-5522-47a4-8f56-51d978306805","attemptId":"f02e1608-64bc-482c-a9b2-c91b545bfd2a","publishAttempt":1,"successCount":1,"relayAttemptedCount":1,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":1,"elapsedMs":0}],"totalElapsedMs":0} -# Published to 1/1 relays (required=1, timeout=200ms) -# [publish:primary] Publishing to 1 relays (wss://healthy)... -# {"event":"lock_publish_quorum_met","correlationId":"f68d409f-2ab6-44f4-a904-b8889886c008","attemptId":"83e634c5-da54-4a31-91d1-6cb33d375bfd","publishAttempt":1,"successCount":1,"relayAttemptedCount":1,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":1,"elapsedMs":0}],"totalElapsedMs":0} -# Published to 1/1 relays (required=1, timeout=200ms) -# [publish:primary] Publishing to 2 relays (wss://primary-a, wss://primary-b)... -# [publish:fallback] Publishing to 1 relays (wss://fallback-healthy)... -# {"event":"lock_publish_quorum_met","correlationId":"958523e2-8ffc-4f3f-951b-de43399d6ee9","attemptId":"0877972b-aa3e-4544-8166-2f04378ceebe","publishAttempt":1,"successCount":1,"relayAttemptedCount":3,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":3,"elapsedMs":1}],"totalElapsedMs":1} -# Published to 1/3 relays (required=1, timeout=200ms) - # Subtest: queryLocks - # Subtest: falls back to fallback relays when primary query fails - ok 1 - falls back to fallback relays when primary query fails - --- - duration_ms: 31.546086 - type: 'test' - ... - 1..1 - ok 2 - queryLocks - --- - duration_ms: 32.037748 - type: 'suite' - ... - # Subtest: publishLock - # Subtest: retries transient failures and succeeds on a later attempt - ok 1 - retries transient failures and succeeds on a later attempt - --- - duration_ms: 5.175407 - type: 'test' - ... - # Subtest: fails after retry budget is exhausted for persistent transient failures - ok 2 - fails after retry budget is exhausted for persistent transient failures - --- - duration_ms: 4.299678 - type: 'test' - ... - # Subtest: fails without retry for persistent validation failures - ok 3 - fails without retry for persistent validation failures - --- - duration_ms: 1.334471 - type: 'test' - ... - # Subtest: includes per-relay reason categories and retry metadata in failure diagnostics - ok 4 - includes per-relay reason categories and retry metadata in failure diagnostics - --- - duration_ms: 4.517275 - type: 'test' - ... - # Subtest: returns success when mixed relay outcomes still satisfy quorum - ok 5 - returns success when mixed relay outcomes still satisfy quorum - --- - duration_ms: 1.939641 - type: 'test' - ... - # Subtest: uses fallback relays to satisfy min success quorum - ok 6 - uses fallback relays to satisfy min success quorum - --- - duration_ms: 1.185694 - type: 'test' - ... - # Subtest: quarantines repeatedly failing relays and still reaches quorum with one healthy relay - ok 7 - quarantines repeatedly failing relays and still reaches quorum with one healthy relay - --- - duration_ms: 2.560686 - type: 'test' - ... - # Subtest: uses fallback and min active pool to reintroduce quarantined relay when needed - ok 8 - uses fallback and min active pool to reintroduce quarantined relay when needed - --- - duration_ms: 1.166479 - type: 'test' - ... - 1..8 - ok 3 - publishLock - --- - duration_ms: 23.155252 - type: 'suite' - ... - 1..3 -ok 33 - lock-ops - --- - duration_ms: 61.54811 - type: 'suite' - ... -# Warning: Failed to read log dir logs/daily: Permission denied -# Subtest: lock-utils - # Subtest: getCompletedAgents - # Subtest: returns completed agents for daily cadence - ok 1 - returns completed agents for daily cadence - --- - duration_ms: 2.287582 - type: 'test' - ... - # Subtest: returns completed agents for weekly cadence - ok 2 - returns completed agents for weekly cadence - --- - duration_ms: 0.440654 - type: 'test' - ... - # Subtest: handles ENOENT gracefully - ok 3 - handles ENOENT gracefully - --- - duration_ms: 0.813141 - type: 'test' - ... - # Subtest: logs error for non-ENOENT errors - ok 4 - logs error for non-ENOENT errors - --- - duration_ms: 1.559218 - type: 'test' - ... - 1..4 - ok 1 - getCompletedAgents - --- - duration_ms: 6.311736 - type: 'suite' - ... - 1..1 -ok 34 - lock-utils - --- - duration_ms: 7.270539 - type: 'suite' - ... -# Subtest: main dispatch - # Subtest: should exit with code 1 if no command provided - ok 1 - should exit with code 1 if no command provided - --- - duration_ms: 1.902829 - type: 'test' - ... - # Subtest: should exit with code 1 if unknown command provided - ok 2 - should exit with code 1 if unknown command provided - --- - duration_ms: 0.546136 - type: 'test' - ... - # Subtest: should exit with code 1 if check command misses cadence - ok 3 - should exit with code 1 if check command misses cadence - --- - duration_ms: 0.691764 - type: 'test' - ... - # Subtest: should exit with code 1 if lock command misses agent - ok 4 - should exit with code 1 if lock command misses agent - --- - duration_ms: 0.811181 - type: 'test' - ... - # Subtest: should exit with code 1 if complete command misses agent - ok 5 - should exit with code 1 if complete command misses agent - --- - duration_ms: 0.797473 - type: 'test' - ... - # Subtest: should exit with code 1 if proposal command misses subcommand - ok 6 - should exit with code 1 if proposal command misses subcommand - --- - duration_ms: 0.471366 - type: 'test' - ... - 1..6 -ok 35 - main dispatch - --- - duration_ms: 133.289648 - type: 'suite' - ... -# Subtest: listMemories, inspectMemory, pinMemory, and unpinMemory support admin hooks -ok 36 - listMemories, inspectMemory, pinMemory, and unpinMemory support admin hooks - --- - duration_ms: 3.521977 - type: 'test' - ... -# Subtest: triggerPruneDryRun returns candidate metadata without deleting records -ok 37 - triggerPruneDryRun returns candidate metadata without deleting records - --- - duration_ms: 0.693638 - type: 'test' - ... -# Subtest: memoryStats reports counts/rates and retrieval telemetry remains redaction-safe -ok 38 - memoryStats reports counts/rates and retrieval telemetry remains redaction-safe - --- - duration_ms: 19.844968 - type: 'test' - ... -# Subtest: runPruneCycle respects prune feature-flag modes -ok 39 - runPruneCycle respects prune feature-flag modes - --- - duration_ms: 0.935193 - type: 'test' - ... -# Subtest: markMemoryMerged delegates to repository and clears cache -ok 40 - markMemoryMerged delegates to repository and clears cache - --- - duration_ms: 0.886653 - type: 'test' - ... -# Subtest: stores runtime events in signer/session namespace and clears scope -ok 41 - stores runtime events in signer/session namespace and clears scope - --- - duration_ms: 3.053576 - type: 'test' - ... -# Subtest: expires runtime events and records expiration metrics -ok 42 - expires runtime events and records expiration metrics - --- - duration_ms: 0.498446 - type: 'test' - ... -# Subtest: enforces LRU bounds and tracks eviction metrics -ok 43 - enforces LRU bounds and tracks eviction metrics - --- - duration_ms: 0.590057 - type: 'test' - ... -# Subtest: blocks decrypted/session-sensitive data from durable promotion without sanitizer pass -ok 44 - blocks decrypted/session-sensitive data from durable promotion without sanitizer pass - --- - duration_ms: 1.801818 - type: 'test' - ... -# Subtest: createEmbedderAdapter selects in-memory backend and supports vector CRUD -ok 45 - createEmbedderAdapter selects in-memory backend and supports vector CRUD - --- - duration_ms: 1.791328 - type: 'test' - ... -# Subtest: createEmbedderAdapter reads backend from environment -ok 46 - createEmbedderAdapter reads backend from environment - --- - duration_ms: 0.553151 - type: 'test' - ... -# Subtest: memory feature flags parse defaults and canary allow-lists -ok 47 - memory feature flags parse defaults and canary allow-lists - --- - duration_ms: 1.525697 - type: 'test' - ... -# Subtest: memory prune flag supports off, dry-run, and active modes -ok 48 - memory prune flag supports off, dry-run, and active modes - --- - duration_ms: 0.393658 - type: 'test' - ... -# Subtest: estimateTokenCount returns heuristic count -ok 49 - estimateTokenCount returns heuristic count - --- - duration_ms: 1.765753 - type: 'test' - ... -# Subtest: formatMemoryBlock converts memory record to display block -ok 50 - formatMemoryBlock converts memory record to display block - --- - duration_ms: 0.88039 - type: 'test' - ... -# Subtest: formatMemoryBlock handles missing optional fields and default clamping -ok 51 - formatMemoryBlock handles missing optional fields and default clamping - --- - duration_ms: 0.225737 - type: 'test' - ... -# Subtest: formatMemoryBlock respects custom clamping options -ok 52 - formatMemoryBlock respects custom clamping options - --- - duration_ms: 0.332773 - type: 'test' - ... -# Subtest: formatMemoryBlock handles edge cases for clamping -ok 53 - formatMemoryBlock handles edge cases for clamping - --- - duration_ms: 0.344988 - type: 'test' - ... -# Subtest: formatMemoriesForPrompt enforces token budget and k limit -ok 54 - formatMemoriesForPrompt enforces token budget and k limit - --- - duration_ms: 0.936284 - type: 'test' - ... -# Subtest: formatMemoriesForPrompt handles aggressive budget fitting -ok 55 - formatMemoriesForPrompt handles aggressive budget fitting - --- - duration_ms: 0.536398 - type: 'test' - ... -# Subtest: renderPromptWithMemoryContext injects context when enabled -ok 56 - renderPromptWithMemoryContext injects context when enabled - --- - duration_ms: 0.871774 - type: 'test' - ... -# Subtest: renderPromptWithMemoryContext returns base prompt when disabled -ok 57 - renderPromptWithMemoryContext returns base prompt when disabled - --- - duration_ms: 0.615498 - type: 'test' - ... -# Subtest: renderPromptWithMemoryContext handles missing query or service -ok 58 - renderPromptWithMemoryContext handles missing query or service - --- - duration_ms: 0.733896 - type: 'test' - ... -# Subtest: renderPromptWithMemoryContext handles circular agentContext gracefully -ok 59 - renderPromptWithMemoryContext handles circular agentContext gracefully - --- - duration_ms: 0.678132 - type: 'test' - ... -# Subtest: renderPromptWithMemoryContext handles agentContext as array of strings -ok 60 - renderPromptWithMemoryContext handles agentContext as array of strings - --- - duration_ms: 0.2995 - type: 'test' - ... -# Subtest: renderPromptWithMemoryContext handles agentContext as plain object -ok 61 - renderPromptWithMemoryContext handles agentContext as plain object - --- - duration_ms: 0.358063 - type: 'test' - ... -# Subtest: renderPromptWithMemoryContext handles agentContext as primitives -ok 62 - renderPromptWithMemoryContext handles agentContext as primitives - --- - duration_ms: 0.256864 - type: 'test' - ... -# Subtest: renderPromptWithMemoryContext calls updateMemoryUsage for retrieved memories -ok 63 - renderPromptWithMemoryContext calls updateMemoryUsage for retrieved memories - --- - duration_ms: 2.907622 - type: 'test' - ... -# Subtest: renderPromptWithMemoryContext handles empty memories gracefully -ok 64 - renderPromptWithMemoryContext handles empty memories gracefully - --- - duration_ms: 0.21904 - type: 'test' - ... -# Subtest: renderPromptWithMemoryContext handles null/undefined memories gracefully -ok 65 - renderPromptWithMemoryContext handles null/undefined memories gracefully - --- - duration_ms: 0.268867 - type: 'test' - ... -# Subtest: renderPromptWithMemoryContext respects TORCH_MEMORY_ENABLED=false -ok 66 - renderPromptWithMemoryContext respects TORCH_MEMORY_ENABLED=false - --- - duration_ms: 0.178048 - type: 'test' - ... -# Subtest: renderPromptWithMemoryContext respects retrieval allowlist -ok 67 - renderPromptWithMemoryContext respects retrieval allowlist - --- - duration_ms: 0.42132 - type: 'test' - ... -# Subtest: renderPromptWithMemoryContext passes k and tokenBudget -ok 68 - renderPromptWithMemoryContext passes k and tokenBudget - --- - duration_ms: 0.460758 - type: 'test' - ... -# Subtest: getRelevantMemories uses cached results when available -ok 69 - getRelevantMemories uses cached results when available - --- - duration_ms: 2.176582 - type: 'test' - ... -# Subtest: getRelevantMemories calls ranker and updates cache on miss -ok 70 - getRelevantMemories calls ranker and updates cache on miss - --- - duration_ms: 1.153101 - type: 'test' - ... -# Subtest: getRelevantMemories passes correct parameters to ranker -ok 71 - getRelevantMemories passes correct parameters to ranker - --- - duration_ms: 0.940401 - type: 'test' - ... -# Subtest: getRelevantMemories triggers updateMemoryUsage on repository -ok 72 - getRelevantMemories triggers updateMemoryUsage on repository - --- - duration_ms: 0.608552 - type: 'test' - ... -# Subtest: ingestEvents - # Subtest: happy path: successfully ingests events - ok 1 - happy path: successfully ingests events - --- - duration_ms: 6.84756 - type: 'test' - ... - # Subtest: skips ingestion when feature flag is disabled - ok 2 - skips ingestion when feature flag is disabled - --- - duration_ms: 0.580305 - type: 'test' - ... - # Subtest: clears cache after ingestion - ok 3 - clears cache after ingestion - --- - duration_ms: 1.937208 - type: 'test' - ... - # Subtest: skips duplicate windows - ok 4 - skips duplicate windows - --- - duration_ms: 1.103867 - type: 'test' - ... - # Subtest: propagates repository errors - ok 5 - propagates repository errors - --- - duration_ms: 1.65817 - type: 'test' - ... - 1..5 -ok 73 - ingestEvents - --- - duration_ms: 13.825534 - type: 'suite' - ... -# Subtest: ingestMemoryWindow gathers sources, redacts pii, chunks, embeds, links, and emits telemetry -ok 74 - ingestMemoryWindow gathers sources, redacts pii, chunks, embeds, links, and emits telemetry - --- - duration_ms: 6.616005 - type: 'test' - ... -# Subtest: ingestMemoryWindow dedupes overlapping windows by hashed content + agent + timestamp bucket -ok 75 - ingestMemoryWindow dedupes overlapping windows by hashed content + agent + timestamp bucket - --- - duration_ms: 0.912107 - type: 'test' - ... -# Subtest: pinMemory pins the memory and clears the cache -ok 76 - pinMemory pins the memory and clears the cache - --- - duration_ms: 3.000409 - type: 'test' - ... -# Subtest: pinMemory returns null for non-existent ID but still clears cache -ok 77 - pinMemory returns null for non-existent ID but still clears cache - --- - duration_ms: 0.538917 - type: 'test' - ... -# Subtest: unpinMemory unpins the memory and clears the cache -ok 78 - unpinMemory unpins the memory and clears the cache - --- - duration_ms: 0.720944 - type: 'test' - ... -# Subtest: unpinMemory returns null for non-existent ID but still clears cache -ok 79 - unpinMemory returns null for non-existent ID but still clears cache - --- - duration_ms: 0.745425 - type: 'test' - ... -# Subtest: createLifecyclePlan respects delete safety constraints and logs reasons -ok 80 - createLifecyclePlan respects delete safety constraints and logs reasons - --- - duration_ms: 5.621406 - type: 'test' - ... -# Subtest: createLifecyclePlan condenses near-duplicates and archives merged source records -ok 81 - createLifecyclePlan condenses near-duplicates and archives merged source records - --- - duration_ms: 1.183292 - type: 'test' - ... -# Subtest: applyLifecycleActions executes keep/archive/delete and merge markers -ok 82 - applyLifecycleActions executes keep/archive/delete and merge markers - --- - duration_ms: 15.182403 - type: 'test' - ... -# Subtest: filterAndRankMemories applies metadata filters and composite score -ok 83 - filterAndRankMemories applies metadata filters and composite score - --- - duration_ms: 2.985244 - type: 'test' - ... -# Subtest: formatMemoriesForPrompt returns prompt blocks and enforces token budget trimming -ok 84 - formatMemoriesForPrompt returns prompt blocks and enforces token budget trimming - --- - duration_ms: 1.862678 - type: 'test' - ... -# Subtest: isMemoryRetrievalEnabled supports global toggle and agent canary list -ok 85 - isMemoryRetrievalEnabled supports global toggle and agent canary list - --- - duration_ms: 0.419468 - type: 'test' - ... -# Subtest: renderPromptWithMemoryContext injects bounded memory block before base prompt -ok 86 - renderPromptWithMemoryContext injects bounded memory block before base prompt - --- - duration_ms: 1.07539 - type: 'test' - ... -# Subtest: runPruneCycle respects off mode -ok 87 - runPruneCycle respects off mode - --- - duration_ms: 23.293266 - type: 'test' - ... -# Subtest: runPruneCycle respects dry-run mode -ok 88 - runPruneCycle respects dry-run mode - --- - duration_ms: 0.584868 - type: 'test' - ... -# Subtest: runPruneCycle removes stale unpinned memories in active mode -ok 89 - runPruneCycle removes stale unpinned memories in active mode - --- - duration_ms: 4.367926 - type: 'test' - ... -# Subtest: runPruneCycle starts scheduler if requested -ok 90 - runPruneCycle starts scheduler if requested - --- - duration_ms: 1002.895969 - type: 'test' - ... -# (node:5744) ExperimentalWarning: The MockTimers API is an experimental feature and might change at any time -# (Use `node --trace-warnings ...` to show where the warning was created) -# Subtest: createDbAdvisoryLockProvider acquires and releases postgres advisory locks -ok 91 - createDbAdvisoryLockProvider acquires and releases postgres advisory locks - --- - duration_ms: 2.372219 - type: 'test' - ... -# Subtest: startMemoryMaintenanceScheduler runs all jobs and emits metrics including retries -ok 92 - startMemoryMaintenanceScheduler runs all jobs and emits metrics including retries - --- - duration_ms: 9.414194 - type: 'test' - ... -# Subtest: startMemoryMaintenanceScheduler skips jobs when feature flags disable memory -ok 93 - startMemoryMaintenanceScheduler skips jobs when feature flags disable memory - --- - duration_ms: 1.757117 - type: 'test' - ... -# Subtest: normalizeMemoryItem applies durable schema defaults and coercions -ok 94 - normalizeMemoryItem applies durable schema defaults and coercions - --- - duration_ms: 2.634006 - type: 'test' - ... -# Subtest: validateMemoryItem reports field-level errors -ok 95 - validateMemoryItem reports field-level errors - --- - duration_ms: 0.593044 - type: 'test' - ... -# Subtest: ingestEvents rejects invalid writes and logs structured validation details -ok 96 - ingestEvents rejects invalid writes and logs structured validation details - --- - duration_ms: 4.796237 - type: 'test' - ... -# (node:5762) ExperimentalWarning: The MockTimers API is an experimental feature and might change at any time -# (Use `node --trace-warnings ...` to show where the warning was created) -# Subtest: memoryStats with empty repository returns zero counts and rates -ok 97 - memoryStats with empty repository returns zero counts and rates - --- - duration_ms: 2.624703 - type: 'test' - ... -# Subtest: memoryStats with populated repository returns correct counts and totals -ok 98 - memoryStats with populated repository returns correct counts and totals - --- - duration_ms: 0.472069 - type: 'test' - ... -# Subtest: memoryStats calculates ingest throughput based on window -ok 99 - memoryStats calculates ingest throughput based on window - --- - duration_ms: 6.628626 - type: 'test' - ... -# Subtest: memoryStats reflects deleted count from prune cycle -ok 100 - memoryStats reflects deleted count from prune cycle - --- - duration_ms: 10.152649 - type: 'test' - ... -# Subtest: memoryStats handles windowMs parameter correctly -ok 101 - memoryStats handles windowMs parameter correctly - --- - duration_ms: 0.699411 - type: 'test' - ... -# Subtest: loadMemoryPromptTemplates loads all memory prompt templates from disk -ok 102 - loadMemoryPromptTemplates loads all memory prompt templates from disk - --- - duration_ms: 1.542175 - type: 'test' - ... -# Subtest: summarizeEvents retries with repair prompt when first response is malformed JSON -ok 103 - summarizeEvents retries with repair prompt when first response is malformed JSON - --- - duration_ms: 2.223192 - type: 'test' - ... -# Subtest: summarizeEvents falls back to deterministic minimal summary with conservative importance when parsing fails twice -ok 104 - summarizeEvents falls back to deterministic minimal summary with conservative importance when parsing fails twice - --- - duration_ms: 0.531995 - type: 'test' - ... -# Subtest: telemetry should not log to stdout or stderr by default -ok 105 - telemetry should not log to stdout or stderr by default - --- - duration_ms: 74.217834 - type: 'test' - ... -# Subtest: telemetry should log to stderr when NODE_DEBUG=torch-memory is set -ok 106 - telemetry should log to stderr when NODE_DEBUG=torch-memory is set - --- - duration_ms: 68.985533 - type: 'test' - ... -# Subtest: schema validation covers required fields, coercion, and rejection paths -ok 107 - schema validation covers required fields, coercion, and rejection paths - --- - duration_ms: 3.197948 - type: 'test' - ... -# Subtest: summarizer parser accepts valid json and recovers after malformed json response -ok 108 - summarizer parser accepts valid json and recovers after malformed json response - --- - duration_ms: 4.289933 - type: 'test' - ... -# Subtest: ingest integration persists memory records and links embeddings from event batches -ok 109 - ingest integration persists memory records and links embeddings from event batches - --- - duration_ms: 2.750969 - type: 'test' - ... -# Subtest: retrieval ranking blends semantic, importance, and recency signals -ok 110 - retrieval ranking blends semantic, importance, and recency signals - --- - duration_ms: 0.996367 - type: 'test' - ... -# Subtest: pruner policy protects pins, merges duplicates, and picks archive/delete outcomes -ok 111 - pruner policy protects pins, merges duplicates, and picks archive/delete outcomes - --- - duration_ms: 1.625259 - type: 'test' - ... -# Subtest: scheduler smoke test handles mocked clock and lock contention -ok 112 - scheduler smoke test handles mocked clock and lock contention - --- - duration_ms: 11.972148 - type: 'test' - ... -# Subtest: CLI Smoke Test - # Subtest: should print usage when no args provided - ok 1 - should print usage when no args provided - --- - duration_ms: 183.873909 - type: 'test' - ... - # Subtest: should fail when checking without cadence - ok 2 - should fail when checking without cadence - --- - duration_ms: 191.296015 - type: 'test' - ... - # Subtest: should include paused agents in check output - ok 3 - should include paused agents in check output - --- - duration_ms: 275.607877 - type: 'test' - ... - 1..3 -ok 113 - CLI Smoke Test - --- - duration_ms: 652.812764 - type: 'suite' - ... -# Initializing torch in /app/test-gitignore-oKDe5r/torch... -# Copying application directories... -# Copied src/ -# Copied bin/ -# Copied dashboard/ -# Copied landing/ -# Copied assets/ -# Copied scripts/ -# Copying application files... -# Copied package.json -# Copied build.mjs -# Copied README.md -# Copied torch-config.example.json -# Copied TORCH.md -# Created torch/roster.json -# Created torch/META_PROMPTS.md -# Created torch/scheduler-flow.md -# Created torch/daily-scheduler.md -# Created torch/weekly-scheduler.md -# Created 23 files in torch/prompts/daily/ -# Created 21 files in torch/prompts/weekly/ -# Created test-gitignore-oKDe5r/torch/.gitignore with node_modules -# Saved configuration to torch-config.json -# Created torch/TORCH_DASHBOARD.md -# No package.json found in host root. Skipping script injection. -# Initialization complete. -# You can now customize the files in torch/ -# ● From torch-config.json: -# - Hashtag: test-hash -# - Namespace: test-ns -# https://torch.thepr0m3th3an.net/dashboard/?hashtag=test-hash&namespace=test-ns&relays=wss%3A%2F%2Frelay.damus.io -# Initializing torch in /app/test-gitignore-existing-jdi5Aq/torch... -# Copying application directories... -# Copied src/ -# Copied bin/ -# Copied dashboard/ -# Copied landing/ -# Copied assets/ -# Copied scripts/ -# Copying application files... -# Copied package.json -# Copied build.mjs -# Copied README.md -# Copied torch-config.example.json -# Copied TORCH.md -# Created torch/roster.json -# Created torch/META_PROMPTS.md -# Created torch/scheduler-flow.md -# Created torch/daily-scheduler.md -# Created torch/weekly-scheduler.md -# Created 23 files in torch/prompts/daily/ -# Created 21 files in torch/prompts/weekly/ -# Updated test-gitignore-existing-jdi5Aq/torch/.gitignore: added node_modules -# Saved configuration to torch-config.json -# Created torch/TORCH_DASHBOARD.md -# Initialization complete. -# No package.json found in host root. Skipping script injection. -# You can now customize the files in torch/ -# ● From torch-config.json: -# - Hashtag: test-hash -# - Namespace: test-ns -# https://torch.thepr0m3th3an.net/dashboard/?hashtag=test-hash&namespace=test-ns&relays=wss%3A%2F%2Frelay.damus.io -# Subtest: ops.mjs: cmdInit creates .gitignore with node_modules -ok 114 - ops.mjs: cmdInit creates .gitignore with node_modules - --- - duration_ms: 84.379516 - type: 'test' - ... -# Subtest: ops.mjs: cmdInit appends to existing .gitignore -ok 115 - ops.mjs: cmdInit appends to existing .gitignore - --- - duration_ms: 66.203768 - type: 'test' - ... -# No TORCH installation detected in this directory. -# Looked for: -# - A torch/ subdirectory containing roster.json or bin/ -# - A package.json with name "torch-lock" (root install) -# Initializing torch in /app/test-ops-remove-KRPlyX/project-force/torch... -# Copying application directories... -# Copied src/ -# Copied bin/ -# Copied dashboard/ -# Copied landing/ -# Copied assets/ -# Copied scripts/ -# Copying application files... -# Copied package.json -# Copied build.mjs -# Copied README.md -# Copied torch-config.example.json -# Copied TORCH.md -# Created torch/roster.json -# Created torch/META_PROMPTS.md -# Created torch/scheduler-flow.md -# Created torch/daily-scheduler.md -# Created torch/weekly-scheduler.md -# Created 23 files in torch/prompts/daily/ -# Created 21 files in torch/prompts/weekly/ -# Created test-ops-remove-KRPlyX/project-force/torch/.gitignore with node_modules -# Saved configuration to torch-config.json -# Created torch/TORCH_DASHBOARD.md -# Initialization complete. -# You can now customize the files in torch/ -# ● From torch-config.json: -# - Hashtag: test-namespace-agent-lock -# - Namespace: test-namespace -# https://torch.thepr0m3th3an.net/dashboard/?hashtag=test-namespace-agent-lock&namespace=test-namespace&relays=wss%3A%2F%2Frelay.damus.io -# The following TORCH artifacts will be removed: -# - torch/ (TORCH install directory) -# - torch-config.json -# - .torch/ (prompt history) -# - src/proposals/ (governance proposals) -# - torch-lock npm package (via npm uninstall) -# Removing TORCH... -# No package.json found in host root. Skipping script injection. -# Removed torch/ (TORCH install directory) -# Removed torch-config.json -# Removed .torch/ (prompt history) -# Removed src/proposals/ (governance proposals) -# Removed empty src/ directory -# Running npm uninstall torch-lock... -# Uninstalled torch-lock package. -# TORCH has been completely removed from this project. -# If you used TORCH environment variables (NOSTR_LOCK_*, TORCH_*), -# remember to remove them from your shell profile or CI configuration. -# Initializing torch in /app/test-ops-remove-KRPlyX/project-runtime/torch... -# Copying application directories... -# Copied src/ -# Copied bin/ -# Copied dashboard/ -# Copied landing/ -# Copied assets/ -# Copied scripts/ -# Copying application files... -# Copied package.json -# Copied build.mjs -# Copied README.md -# Copied torch-config.example.json -# Copied TORCH.md -# Created torch/roster.json -# Created torch/META_PROMPTS.md -# Created torch/scheduler-flow.md -# Created torch/daily-scheduler.md -# Created torch/weekly-scheduler.md -# Created 23 files in torch/prompts/daily/ -# Created 21 files in torch/prompts/weekly/ -# Created test-ops-remove-KRPlyX/project-runtime/torch/.gitignore with node_modules -# Saved configuration to torch-config.json -# Created torch/TORCH_DASHBOARD.md -# Initialization complete. -# You can now customize the files in torch/ -# ● From torch-config.json: -# - Hashtag: test-namespace-agent-lock -# - Namespace: test-namespace -# No package.json found in host root. Skipping script injection. -# https://torch.thepr0m3th3an.net/dashboard/?hashtag=test-namespace-agent-lock&namespace=test-namespace&relays=wss%3A%2F%2Frelay.damus.io -# The following TORCH artifacts will be removed: -# - torch/ (TORCH install directory) -# - torch-config.json -# - .torch/ (prompt history) -# - .scheduler-memory/ (memory store) -# - task-logs/ (scheduler logs) -# - src/proposals/ (governance proposals) -# - torch-lock npm package (via npm uninstall) -# Removing TORCH... -# Removed torch/ (TORCH install directory) -# Removed torch-config.json -# Removed .torch/ (prompt history) -# Removed .scheduler-memory/ (memory store) -# Removed task-logs/ (scheduler logs) -# Removed src/proposals/ (governance proposals) -# Removed empty src/ directory -# Running npm uninstall torch-lock... -# Uninstalled torch-lock package. -# TORCH has been completely removed from this project. -# If you used TORCH environment variables (NOSTR_LOCK_*, TORCH_*), -# remember to remove them from your shell profile or CI configuration. -# Initializing torch in /app/test-ops-remove-KRPlyX/project-proposals/torch... -# Copying application directories... -# Copied src/ -# Copied bin/ -# Copied dashboard/ -# Copied landing/ -# Copied assets/ -# Copied scripts/ -# Copying application files... -# Copied package.json -# Copied build.mjs -# Copied README.md -# Copied torch-config.example.json -# Copied TORCH.md -# Created torch/roster.json -# Created torch/META_PROMPTS.md -# Created torch/scheduler-flow.md -# Created torch/daily-scheduler.md -# Created torch/weekly-scheduler.md -# Created 23 files in torch/prompts/daily/ -# Created 21 files in torch/prompts/weekly/ -# Created test-ops-remove-KRPlyX/project-proposals/torch/.gitignore with node_modules -# Saved configuration to torch-config.json -# Created torch/TORCH_DASHBOARD.md -# Initialization complete. -# You can now customize the files in torch/ -# No package.json found in host root. Skipping script injection. -# ● From torch-config.json: -# - Hashtag: test-namespace-agent-lock -# - Namespace: test-namespace -# https://torch.thepr0m3th3an.net/dashboard/?hashtag=test-namespace-agent-lock&namespace=test-namespace&relays=wss%3A%2F%2Frelay.damus.io -# The following TORCH artifacts will be removed: -# - torch/ (TORCH install directory) -# - torch-config.json -# - .torch/ (prompt history) -# - src/proposals/ (governance proposals) -# - torch-lock npm package (via npm uninstall) -# Removing TORCH... -# Removed torch/ (TORCH install directory) -# Removed torch-config.json -# Removed .torch/ (prompt history) -# Removed src/proposals/ (governance proposals) -# Removed empty src/ directory -# Running npm uninstall torch-lock... -# Uninstalled torch-lock package. -# TORCH has been completely removed from this project. -# If you used TORCH environment variables (NOSTR_LOCK_*, TORCH_*), -# remember to remove them from your shell profile or CI configuration. -# Initializing torch in /app/test-ops-remove-KRPlyX/project-scripts/torch... -# Copying application directories... -# Copied src/ -# Copied bin/ -# Copied dashboard/ -# Copied landing/ -# Copied assets/ -# Copied scripts/ -# Copying application files... -# Copied package.json -# Copied build.mjs -# Copied README.md -# Copied torch-config.example.json -# Copied TORCH.md -# Created torch/roster.json -# Created torch/META_PROMPTS.md -# Created torch/scheduler-flow.md -# Created torch/daily-scheduler.md -# Created torch/weekly-scheduler.md -# Created 23 files in torch/prompts/daily/ -# Created 21 files in torch/prompts/weekly/ -# Created test-ops-remove-KRPlyX/project-scripts/torch/.gitignore with node_modules -# Saved configuration to torch-config.json -# Created torch/TORCH_DASHBOARD.md -# Added script: "torch:dashboard" -# Added script: "torch:check" -# Added script: "torch:lock" -# Added script: "torch:health" -# Added script: "torch:memory:list" -# Added script: "torch:memory:inspect" -# Updated package.json with convenience scripts. -# Initialization complete. -# You can now customize the files in torch/ -# ● From torch-config.json: -# - Hashtag: test-namespace-agent-lock -# - Namespace: test-namespace -# https://torch.thepr0m3th3an.net/dashboard/?hashtag=test-namespace-agent-lock&namespace=test-namespace&relays=wss%3A%2F%2Frelay.damus.io -# The following TORCH artifacts will be removed: -# - torch/ (TORCH install directory) -# - torch-config.json -# - .torch/ (prompt history) -# - src/proposals/ (governance proposals) -# - torch:* scripts from package.json -# - torch-lock npm package (via npm uninstall) -# Removing TORCH... -# Removed torch/ (TORCH install directory) -# Removed torch-config.json -# Removed .torch/ (prompt history) -# Removed src/proposals/ (governance proposals) -# Removed empty src/ directory -# Removed script: "torch:dashboard" -# Removed script: "torch:check" -# Removed script: "torch:lock" -# Removed script: "torch:health" -# Removed script: "torch:memory:list" -# Removed script: "torch:memory:inspect" -# Updated package.json. -# Running npm uninstall torch-lock... -# Uninstalled torch-lock package. -# TORCH has been completely removed from this project. -# If you used TORCH environment variables (NOSTR_LOCK_*, TORCH_*), -# remember to remove them from your shell profile or CI configuration. -# Initializing torch in /app/test-ops-remove-KRPlyX/project-cancel/torch... -# Copying application directories... -# Copied src/ -# Copied bin/ -# Copied dashboard/ -# Copied landing/ -# Copied assets/ -# Copied scripts/ -# Copying application files... -# Copied package.json -# Copied build.mjs -# Copied README.md -# Copied torch-config.example.json -# Copied TORCH.md -# Created torch/roster.json -# Created torch/META_PROMPTS.md -# Created torch/scheduler-flow.md -# Created torch/daily-scheduler.md -# Created torch/weekly-scheduler.md -# Created 23 files in torch/prompts/daily/ -# Created 21 files in torch/prompts/weekly/ -# Created test-ops-remove-KRPlyX/project-cancel/torch/.gitignore with node_modules -# Saved configuration to torch-config.json -# Created torch/TORCH_DASHBOARD.md -# Initialization complete. -# You can now customize the files in torch/ -# ● From torch-config.json: -# - Hashtag: test-namespace-agent-lock -# - Namespace: test-namespace -# No package.json found in host root. Skipping script injection. -# https://torch.thepr0m3th3an.net/dashboard/?hashtag=test-namespace-agent-lock&namespace=test-namespace&relays=wss%3A%2F%2Frelay.damus.io -# The following TORCH artifacts will be removed: -# - torch/ (TORCH install directory) -# - torch-config.json -# - .torch/ (prompt history) -# - src/proposals/ (governance proposals) -# - torch-lock npm package (via npm uninstall) -# Removal cancelled. -# Initializing torch in /app/test-ops-remove-KRPlyX/project-confirm/torch... -# Copying application directories... -# Copied src/ -# Copied bin/ -# Copied dashboard/ -# Copied landing/ -# Copied assets/ -# Copied scripts/ -# Copying application files... -# Copied package.json -# Copied build.mjs -# Copied README.md -# Copied torch-config.example.json -# Copied TORCH.md -# Created torch/roster.json -# Created torch/META_PROMPTS.md -# Created torch/scheduler-flow.md -# Created torch/daily-scheduler.md -# Created torch/weekly-scheduler.md -# Created 23 files in torch/prompts/daily/ -# Created 21 files in torch/prompts/weekly/ -# Created test-ops-remove-KRPlyX/project-confirm/torch/.gitignore with node_modules -# Saved configuration to torch-config.json -# Created torch/TORCH_DASHBOARD.md -# Initialization complete. -# You can now customize the files in torch/ -# ● From torch-config.json: -# - Hashtag: test-namespace-agent-lock -# - Namespace: test-namespace -# https://torch.thepr0m3th3an.net/dashboard/?hashtag=test-namespace-agent-lock&namespace=test-namespace&relays=wss%3A%2F%2Frelay.damus.io -# The following TORCH artifacts will be removed: -# - torch/ (TORCH install directory) -# - torch-config.json -# - .torch/ (prompt history) -# - src/proposals/ (governance proposals) -# - torch-lock npm package (via npm uninstall) -# Removing TORCH... -# No package.json found in host root. Skipping script injection. -# Removed torch/ (TORCH install directory) -# Removed torch-config.json -# Removed .torch/ (prompt history) -# Removed src/proposals/ (governance proposals) -# Removed empty src/ directory -# Running npm uninstall torch-lock... -# Uninstalled torch-lock package. -# TORCH has been completely removed from this project. -# If you used TORCH environment variables (NOSTR_LOCK_*, TORCH_*), -# remember to remove them from your shell profile or CI configuration. -# Subtest: cmdRemove reports nothing when no TORCH installation exists -ok 116 - cmdRemove reports nothing when no TORCH installation exists - --- - duration_ms: 3.226981 - type: 'test' - ... -# Subtest: cmdRemove with --force removes torch/ directory -ok 117 - cmdRemove with --force removes torch/ directory - --- - duration_ms: 1072.994519 - type: 'test' - ... -# Subtest: cmdRemove removes runtime artifacts (task-logs, .scheduler-memory) -ok 118 - cmdRemove removes runtime artifacts (task-logs, .scheduler-memory) - --- - duration_ms: 981.233924 - type: 'test' - ... -# Subtest: cmdRemove removes src/proposals/ and cleans empty src/ -ok 119 - cmdRemove removes src/proposals/ and cleans empty src/ - --- - duration_ms: 896.778839 - type: 'test' - ... -# Subtest: cmdRemove cleans torch:* scripts from host package.json -ok 120 - cmdRemove cleans torch:* scripts from host package.json - --- - duration_ms: 530.142794 - type: 'test' - ... -# Subtest: cmdRemove cancels when user declines confirmation -ok 121 - cmdRemove cancels when user declines confirmation - --- - duration_ms: 34.568048 - type: 'test' - ... -# Subtest: cmdRemove confirmed via mockAnswers removes artifacts -ok 122 - cmdRemove confirmed via mockAnswers removes artifacts - --- - duration_ms: 835.409159 - type: 'test' - ... -# Initializing torch in /app/test-ops-nHD3IB/project1/torch... -# Copying application directories... -# Copied src/ -# Copied bin/ -# Copied dashboard/ -# Copied landing/ -# Copied assets/ -# Copied scripts/ -# Copying application files... -# Copied package.json -# Copied build.mjs -# Copied README.md -# Copied torch-config.example.json -# Copied TORCH.md -# Created torch/roster.json -# Created torch/META_PROMPTS.md -# Created torch/scheduler-flow.md -# Created torch/daily-scheduler.md -# Created torch/weekly-scheduler.md -# Created 23 files in torch/prompts/daily/ -# Created 21 files in torch/prompts/weekly/ -# Created test-ops-nHD3IB/project1/torch/.gitignore with node_modules -# Saved configuration to torch-config.json -# Created torch/TORCH_DASHBOARD.md -# No package.json found in host root. Skipping script injection. -# Initialization complete. -# You can now customize the files in torch/ -# ● From torch-config.json: -# - Hashtag: test-hashtag -# - Namespace: test-namespace -# https://torch.thepr0m3th3an.net/dashboard/?hashtag=test-hashtag&namespace=test-namespace&relays=wss%3A%2F%2Frelay.damus.io -# Initializing torch in /app/test-ops-nHD3IB/project2/torch... -# Initializing torch in /app/test-ops-nHD3IB/project3/torch... -# Copying application directories... -# Copied src/ -# Copied bin/ -# Copied dashboard/ -# Copied landing/ -# Copied assets/ -# Copied scripts/ -# Copying application files... -# Copied package.json -# Copied build.mjs -# Copied README.md -# Copied torch-config.example.json -# Copied TORCH.md -# Created torch/roster.json -# Created torch/META_PROMPTS.md -# Created torch/scheduler-flow.md -# Created torch/daily-scheduler.md -# Created torch/weekly-scheduler.md -# Created 23 files in torch/prompts/daily/ -# Created 21 files in torch/prompts/weekly/ -# Created test-ops-nHD3IB/project3/torch/.gitignore with node_modules -# Saved configuration to torch-config.json -# Created torch/TORCH_DASHBOARD.md -# Initialization complete. -# You can now customize the files in torch/ -# ● From torch-config.json: -# - Hashtag: test-hashtag -# - Namespace: test-namespace -# https://torch.thepr0m3th3an.net/dashboard/?hashtag=test-hashtag&namespace=test-namespace&relays=wss%3A%2F%2Frelay.damus.io -# Updating torch configuration in /app/test-ops-nHD3IB/project3/torch... -# Creating backup at torch/_backups/backup_2026-02-21T18-29-52-435Z... -# No package.json found in host root. Skipping script injection. -# Updating application directories... -# Updated src/ -# Updated bin/ -# Updated dashboard/ -# Updated landing/ -# Updated assets/ -# Updated scripts/ -# Updating application files... -# Updated package.json -# Updated build.mjs -# Updated README.md -# Updated torch-config.example.json -# Updated TORCH.md -# Updating static files... -# Updated META_PROMPTS.md -# Updated scheduler-flow.md -# Updated daily-scheduler.md -# Updated weekly-scheduler.md -# Skipped roster.json (preserved) -# Updating prompts... -# daily/: 0 added, 0 updated, 23 preserved -# weekly/: 0 added, 0 updated, 21 preserved -# Update complete. -# Initializing torch in /app/test-ops-nHD3IB/project4/torch... -# Copying application directories... -# Copied src/ -# Copied bin/ -# Copied dashboard/ -# Copied landing/ -# Copied assets/ -# Copied scripts/ -# Copying application files... -# Copied package.json -# Copied build.mjs -# Copied README.md -# Copied torch-config.example.json -# Copied TORCH.md -# Created torch/roster.json -# Created torch/META_PROMPTS.md -# Created torch/scheduler-flow.md -# Created torch/daily-scheduler.md -# Created torch/weekly-scheduler.md -# Created 23 files in torch/prompts/daily/ -# Created 21 files in torch/prompts/weekly/ -# Created test-ops-nHD3IB/project4/torch/.gitignore with node_modules -# Saved configuration to torch-config.json -# Created torch/TORCH_DASHBOARD.md -# No package.json found in host root. Skipping script injection. -# Initialization complete. -# You can now customize the files in torch/ -# ● From torch-config.json: -# - Hashtag: test-hashtag -# - Namespace: test-namespace -# https://torch.thepr0m3th3an.net/dashboard/?hashtag=test-hashtag&namespace=test-namespace&relays=wss%3A%2F%2Frelay.damus.io -# Updating torch configuration in /app/test-ops-nHD3IB/project4/torch... -# Creating backup at torch/_backups/backup_2026-02-21T18-29-52-563Z... -# Updating application directories... -# Updated src/ -# Updated bin/ -# Updated dashboard/ -# Updated landing/ -# Updated assets/ -# Updated scripts/ -# Updating application files... -# Updated package.json -# Updated build.mjs -# Updated README.md -# Updated torch-config.example.json -# Updated TORCH.md -# Updating static files... -# Updated META_PROMPTS.md -# Updated scheduler-flow.md -# Updated daily-scheduler.md -# Updated weekly-scheduler.md -# Overwrote roster.json (forced) -# Updating prompts... -# daily/: 0 added, 23 updated, 0 preserved -# weekly/: 0 added, 21 updated, 0 preserved -# Update complete. -# Initializing torch in /app/test-ops-nHD3IB/project5/torch... -# Copying application directories... -# Copied src/ -# Copied bin/ -# Copied dashboard/ -# Copied landing/ -# Copied assets/ -# Copied scripts/ -# Copying application files... -# Copied package.json -# Copied build.mjs -# Copied README.md -# Copied torch-config.example.json -# Copied TORCH.md -# Created torch/roster.json -# Created torch/META_PROMPTS.md -# Created torch/scheduler-flow.md -# Created torch/daily-scheduler.md -# Created torch/weekly-scheduler.md -# Created 23 files in torch/prompts/daily/ -# Created 21 files in torch/prompts/weekly/ -# Created test-ops-nHD3IB/project5/torch/.gitignore with node_modules -# Saved configuration to torch-config.json -# Created torch/TORCH_DASHBOARD.md -# No package.json found in host root. Skipping script injection. -# Initialization complete. -# You can now customize the files in torch/ -# ● From torch-config.json: -# - Hashtag: test-hashtag -# - Namespace: test-namespace -# https://torch.thepr0m3th3an.net/dashboard/?hashtag=test-hashtag&namespace=test-namespace&relays=wss%3A%2F%2Frelay.damus.io -# Updating torch configuration in /app/test-ops-nHD3IB/project5/torch... -# Creating backup at torch/_backups/backup_2026-02-21T18-29-52-756Z... -# Updating application directories... -# Updated src/ -# Updated bin/ -# Updated dashboard/ -# Updated landing/ -# Updated assets/ -# Updated scripts/ -# Updating application files... -# Updated package.json -# Updated build.mjs -# Updated README.md -# Updated torch-config.example.json -# Updated TORCH.md -# Updating static files... -# Updated META_PROMPTS.md -# Updated scheduler-flow.md -# Updated daily-scheduler.md -# Updated weekly-scheduler.md -# Skipped roster.json (preserved) -# Updating prompts... -# daily/: 0 added, 0 updated, 23 preserved -# weekly/: 0 added, 0 updated, 21 preserved -# Update complete. -# Initializing torch in /app/test-ops-nHD3IB/project_roster/torch... -# Copying application directories... -# Copied src/ -# Copied bin/ -# Copied dashboard/ -# Copied landing/ -# Copied assets/ -# Copied scripts/ -# Copying application files... -# Copied package.json -# Copied build.mjs -# Copied README.md -# Copied torch-config.example.json -# Copied TORCH.md -# Created torch/roster.json -# Created torch/META_PROMPTS.md -# Created torch/scheduler-flow.md -# Created torch/daily-scheduler.md -# Created torch/weekly-scheduler.md -# Created 23 files in torch/prompts/daily/ -# Created 21 files in torch/prompts/weekly/ -# Created test-ops-nHD3IB/project_roster/torch/.gitignore with node_modules -# Saved configuration to torch-config.json -# Created torch/TORCH_DASHBOARD.md -# No package.json found in host root. Skipping script injection. -# Initialization complete. -# You can now customize the files in torch/ -# ● From torch-config.json: -# - Hashtag: test-hashtag -# - Namespace: test-namespace -# https://torch.thepr0m3th3an.net/dashboard/?hashtag=test-hashtag&namespace=test-namespace&relays=wss%3A%2F%2Frelay.damus.io -# Checking locks: namespace=test-namespace, cadence=daily, date=2026-02-21 -# Relays: wss://relay.damus.io -# {"event":"relay_health_snapshot","reason":"query:periodic","relays":[{"relay":"wss://relay.damus.io","score":0.6,"sampleSize":0,"successRate":0.5,"timeoutRate":0,"averageLatencyMs":null,"failureStreak":0,"quarantined":false,"quarantineRemainingMs":0,"cooldownMs":30000,"lastResultAt":null},{"relay":"wss://relay.nostr.band","score":0.6,"sampleSize":0,"successRate":0.5,"timeoutRate":0,"averageLatencyMs":null,"failureStreak":0,"quarantined":false,"quarantineRemainingMs":0,"cooldownMs":30000,"lastResultAt":null},{"relay":"wss://relay.snort.social","score":0.6,"sampleSize":0,"successRate":0.5,"timeoutRate":0,"averageLatencyMs":null,"failureStreak":0,"quarantined":false,"quarantineRemainingMs":0,"cooldownMs":30000,"lastResultAt":null}]} -# [query:primary] Querying 1 relays (wss://relay.damus.io)... -# Initializing torch in /app/test-ops-nHD3IB/project_config_init/torch... -# Copying application directories... -# Copied src/ -# Copied bin/ -# Copied dashboard/ -# Copied landing/ -# Copied assets/ -# Copied scripts/ -# Copying application files... -# Copied package.json -# Copied build.mjs -# Copied README.md -# Copied torch-config.example.json -# Copied TORCH.md -# Created torch/roster.json -# Created torch/META_PROMPTS.md -# Created torch/scheduler-flow.md -# Created torch/daily-scheduler.md -# Created torch/weekly-scheduler.md -# Created 23 files in torch/prompts/daily/ -# Created 21 files in torch/prompts/weekly/ -# Created test-ops-nHD3IB/project_config_init/torch/.gitignore with node_modules -# Saved configuration to torch-config.json -# Created torch/TORCH_DASHBOARD.md -# No package.json found in host root. Skipping script injection. -# Initialization complete. -# You can now customize the files in torch/ -# ● From torch-config.json: -# - Hashtag: test-hashtag -# - Namespace: test-namespace -# https://torch.thepr0m3th3an.net/dashboard/?hashtag=test-hashtag&namespace=test-namespace&relays=wss%3A%2F%2Frelay.damus.io -# Subtest: cmdInit creates directory structure and files -ok 123 - cmdInit creates directory structure and files - --- - duration_ms: 53.211962 - type: 'test' - ... -# Subtest: cmdInit fails if directory exists without force -ok 124 - cmdInit fails if directory exists without force - --- - duration_ms: 1.783484 - type: 'test' - ... -# Subtest: cmdUpdate preserves modified prompt -ok 125 - cmdUpdate preserves modified prompt - --- - duration_ms: 118.011716 - type: 'test' - ... -# Subtest: cmdUpdate overwrites prompt with force -ok 126 - cmdUpdate overwrites prompt with force - --- - duration_ms: 165.584589 - type: 'test' - ... -# Subtest: cmdUpdate creates backup -ok 127 - cmdUpdate creates backup - --- - duration_ms: 183.902287 - type: 'test' - ... -# Subtest: torch-lock check respects local roster -ok 128 - torch-lock check respects local roster - --- - duration_ms: 616.394005 - type: 'test' - ... -# Subtest: cmdInit creates torch-config.json with random namespace -ok 129 - cmdInit creates torch-config.json with random namespace - --- - duration_ms: 46.076417 - type: 'test' - ... -# [publish:primary] Publishing to 3 relays (ws://127.0.0.1:37933, ws://127.0.0.1:39313, ws://127.0.0.1:42287)... -# Published to 1/3 relays (required=1, timeout=80ms) -# Subtest: relay fanout quorum integration - # Subtest: passes with required=1 when one relay succeeds and two fail -# [publish:primary] Publishing to 3 relays (ws://127.0.0.1:37833, ws://127.0.0.1:42387, ws://127.0.0.1:46491)... - ok 1 - passes with required=1 when one relay succeeds and two fail - --- - duration_ms: 150.707998 - type: 'test' - ... - # Subtest: fails with quorum error for required=1 when all relays fail - ok 2 - fails with quorum error for required=1 when all relays fail - --- - duration_ms: 92.313968 - type: 'test' - ... -# [publish:primary] Publishing to 3 relays (ws://127.0.0.1:34897, ws://127.0.0.1:37929, ws://127.0.0.1:45367)... - # Subtest: fails with required=2 when only one relay succeeds - ok 3 - fails with required=2 when only one relay succeeds - --- - duration_ms: 94.77822 - type: 'test' - ... -# [publish:primary] Publishing to 3 relays (ws://127.0.0.1:38231, ws://127.0.0.1:40627, ws://127.0.0.1:44605)... -# Published to 2/3 relays (required=2, timeout=80ms) - # Subtest: passes when delayed successes reach quorum before timeout - ok 4 - passes when delayed successes reach quorum before timeout - --- - duration_ms: 90.423426 - type: 'test' - ... - 1..4 -ok 130 - relay fanout quorum integration - --- - duration_ms: 429.902071 - type: 'suite' - ... -# Subtest: RelayHealthManager - # Subtest: should isolate state between instances - ok 1 - should isolate state between instances - --- - duration_ms: 1.15451 - type: 'test' - ... - # Subtest: should prioritize relays based on health - ok 2 - should prioritize relays based on health - --- - duration_ms: 1.829957 - type: 'test' - ... - # Subtest: should reset state correctly - ok 3 - should reset state correctly - --- - duration_ms: 0.223869 - type: 'test' - ... - # Subtest: should cache ranked results and invalidate on outcome - ok 4 - should cache ranked results and invalidate on outcome - --- - duration_ms: 0.60852 - type: 'test' - ... - # Subtest: should invalidate cache when time passes quarantine expiry - ok 5 - should invalidate cache when time passes quarantine expiry - --- - duration_ms: 0.512578 - type: 'test' - ... - 1..5 -ok 131 - RelayHealthManager - --- - duration_ms: 5.957049 - type: 'suite' - ... -# Subtest: evaluateAlertThresholds - # Subtest: Happy Path: No alerts when thresholds are not met - ok 1 - Happy Path: No alerts when thresholds are not met - --- - duration_ms: 1.559334 - type: 'test' - ... - # Subtest: All Relays Down: Triggers alert when duration equals threshold - ok 2 - All Relays Down: Triggers alert when duration equals threshold - --- - duration_ms: 0.41104 - type: 'test' - ... - # Subtest: All Relays Down: Does NOT trigger alert just below threshold - ok 3 - All Relays Down: Does NOT trigger alert just below threshold - --- - duration_ms: 0.270366 - type: 'test' - ... - # Subtest: Success Rate: Triggers alert when rate is strictly below threshold - ok 4 - Success Rate: Triggers alert when rate is strictly below threshold - --- - duration_ms: 0.311448 - type: 'test' - ... - # Subtest: Success Rate: Does NOT trigger alert when rate equals threshold - ok 5 - Success Rate: Does NOT trigger alert when rate equals threshold - --- - duration_ms: 0.38015 - type: 'test' - ... - # Subtest: Success Rate: Does NOT trigger alert when rate is above threshold - ok 6 - Success Rate: Does NOT trigger alert when rate is above threshold - --- - duration_ms: 0.32889 - type: 'test' - ... - # Subtest: Multiple Alerts: Both conditions met - ok 7 - Multiple Alerts: Both conditions met - --- - duration_ms: 0.647577 - type: 'test' - ... - # Subtest: Edge Case: Empty history (should not crash) - ok 8 - Edge Case: Empty history (should not crash) - --- - duration_ms: 0.453544 - type: 'test' - ... - # Subtest: Edge Case: Empty history but current result is unhealthy - ok 9 - Edge Case: Empty history but current result is unhealthy - --- - duration_ms: 0.922324 - type: 'test' - ... - 1..9 -ok 132 - evaluateAlertThresholds - --- - duration_ms: 11.26757 - type: 'test' - ... -# Subtest: summarizeHistory - basic statistics calculation -ok 133 - summarizeHistory - basic statistics calculation - --- - duration_ms: 1.570377 - type: 'test' - ... -# Subtest: summarizeHistory - handles empty history -ok 134 - summarizeHistory - handles empty history - --- - duration_ms: 0.23108 - type: 'test' - ... -# Subtest: summarizeHistory - respects time window for stats -ok 135 - summarizeHistory - respects time window for stats - --- - duration_ms: 4.685077 - type: 'test' - ... -# Subtest: summarizeHistory - allDownDurationMinutes considers outside window entries -ok 136 - summarizeHistory - allDownDurationMinutes considers outside window entries - --- - duration_ms: 0.30836 - type: 'test' - ... -# Subtest: summarizeHistory - allDownDurationMinutes is null if never healthy -ok 137 - summarizeHistory - allDownDurationMinutes is null if never healthy - --- - duration_ms: 0.474925 - type: 'test' - ... -# Subtest: summarizeHistory - allDownDurationMinutes is 0 if currently healthy -ok 138 - summarizeHistory - allDownDurationMinutes is 0 if currently healthy - --- - duration_ms: 0.224911 - type: 'test' - ... -# Subtest: summarizeHistory - handles malformed entries gracefully -ok 139 - summarizeHistory - handles malformed entries gracefully - --- - duration_ms: 0.559416 - type: 'test' - ... -# Subtest: summarizeHistory - handles valid timestamp but missing summary -ok 140 - summarizeHistory - handles valid timestamp but missing summary - --- - duration_ms: 0.214888 - type: 'test' - ... -# Subtest: summarizeHistory - healthy entry with invalid timestamp results in NaN duration -ok 141 - summarizeHistory - healthy entry with invalid timestamp results in NaN duration - --- - duration_ms: 0.750622 - type: 'test' - ... -# Subtest: Roster (Unit Tests with Dependency Injection) - # Subtest: returns fallback roster when no configuration or files exist - ok 1 - returns fallback roster when no configuration or files exist - --- - duration_ms: 3.142597 - type: 'test' - ... - # Subtest: loads internal module roster if user/cwd rosters are missing - ok 2 - loads internal module roster if user/cwd rosters are missing - --- - duration_ms: 1.671994 - type: 'test' - ... - # Subtest: loads roster from torch/roster.json (User Roster) if present - ok 3 - loads roster from torch/roster.json (User Roster) if present - --- - duration_ms: 1.156746 - type: 'test' - ... - # Subtest: loads roster from roster.json (CWD Roster) if present and torch/roster.json is missing - ok 4 - loads roster from roster.json (CWD Roster) if present and torch/roster.json is missing - --- - duration_ms: 0.895417 - type: 'test' - ... - # Subtest: falls back to default if roster file is malformed - ok 5 - falls back to default if roster file is malformed - --- - duration_ms: 0.565235 - type: 'test' - ... - # Subtest: falls back to default if roster file is missing daily/weekly arrays - ok 6 - falls back to default if roster file is missing daily/weekly arrays - --- - duration_ms: 0.621587 - type: 'test' - ... - # Subtest: prioritizes environment variables over file and config - ok 7 - prioritizes environment variables over file and config - --- - duration_ms: 2.302631 - type: 'test' - ... - # Subtest: prioritizes config over file - ok 8 - prioritizes config over file - --- - duration_ms: 0.539688 - type: 'test' - ... - # Subtest: handles multiple items in env var roster - ok 9 - handles multiple items in env var roster - --- - duration_ms: 0.656921 - type: 'test' - ... - 1..9 -ok 142 - Roster (Unit Tests with Dependency Injection) - --- - duration_ms: 13.930639 - type: 'suite' - ... -# Subtest: fails required memory policy when retrieval/storage evidence is missing -ok 143 - fails required memory policy when retrieval/storage evidence is missing - --- - duration_ms: 934.218396 - type: 'test' - ... -# Subtest: accepts required memory policy when markers or artifacts are produced -ok 144 - accepts required memory policy when markers or artifacts are produced - --- - duration_ms: 965.528997 - type: 'test' - ... -# Subtest: records backend failure metadata when lock command exits with code 2 -ok 145 - records backend failure metadata when lock command exits with code 2 - --- - duration_ms: 940.522138 - type: 'test' - ... -# Subtest: defers backend failures in non-strict mode and reuses idempotency key on successful retry -ok 146 - defers backend failures in non-strict mode and reuses idempotency key on successful retry - --- - duration_ms: 661.328702 - type: 'test' - ... -# Subtest: fails after exceeding non-strict deferral budget -ok 147 - fails after exceeding non-strict deferral budget - --- - duration_ms: 219.851849 - type: 'test' - ... -# Subtest: strict lock mode fails immediately without deferral -ok 148 - strict lock mode fails immediately without deferral - --- - duration_ms: 126.21678 - type: 'test' - ... -# Subtest: skips lock health preflight by default when not enabled -ok 149 - skips lock health preflight by default when not enabled - --- - duration_ms: 429.553363 - type: 'test' - ... -# Subtest: fails scheduler with preflight metadata when lock health preflight is enabled -ok 150 - fails scheduler with preflight metadata when lock health preflight is enabled - --- - duration_ms: 145.246518 - type: 'test' - ... -# Subtest: categorizes unreadable prompt files as prompt_parse_error -ok 151 - categorizes unreadable prompt files as prompt_parse_error - --- - duration_ms: 114.589262 - type: 'test' - ... -# Subtest: categorizes invalid prompt schema as prompt_schema_error -ok 152 - categorizes invalid prompt schema as prompt_schema_error - --- - duration_ms: 107.090859 - type: 'test' - ... -# Subtest: categorizes handoff runtime failures as execution_error -ok 153 - categorizes handoff runtime failures as execution_error - --- - duration_ms: 394.639485 - type: 'test' - ... -# Subtest: retries lock acquisition for backend error and succeeds on a later attempt -ok 154 - retries lock acquisition for backend error and succeeds on a later attempt - --- - duration_ms: 447.380668 - type: 'test' - ... -# Subtest: stops retrying after configured lock backend retries are exhausted -ok 155 - stops retrying after configured lock backend retries are exhausted - --- - duration_ms: 126.009159 - type: 'test' - ... -# Subtest: does not use backend retry flow for exit code 3 lock conflicts -ok 156 - does not use backend retry flow for exit code 3 lock conflicts - --- - duration_ms: 497.039294 - type: 'test' - ... -# Subtest: runs selected scheduler prompt through configured runner command -ok 157 - runs selected scheduler prompt through configured runner command - --- - duration_ms: 341.018623 - type: 'test' - ... -# Subtest: propagates non-zero runner exit code -ok 158 - propagates non-zero runner exit code - --- - duration_ms: 324.810772 - type: 'test' - ... -# Subtest: scheduler lock backend failure artifact matches required frontmatter schema -ok 159 - scheduler lock backend failure artifact matches required frontmatter schema - --- - duration_ms: 123.175378 - type: 'test' - ... -# Subtest: schema contract catches missing required lock-backend field -ok 160 - schema contract catches missing required lock-backend field - --- - duration_ms: 2.082935 - type: 'test' - ... -# Subtest: schema contract catches misnamed lock-backend key -ok 161 - schema contract catches misnamed lock-backend key - --- - duration_ms: 0.541037 - type: 'test' - ... -# Subtest: lock preflight e2e: successful lock writes completed status snapshot -ok 162 - lock preflight e2e: successful lock writes completed status snapshot - --- - duration_ms: 478.542263 - type: 'test' - ... -# Subtest: lock preflight e2e: exit code 2 quorum failure persists failed backend status and prompt-not-started marker -ok 163 - lock preflight e2e: exit code 2 quorum failure persists failed backend status and prompt-not-started marker - --- - duration_ms: 115.613689 - type: 'test' - ... -# Subtest: lock preflight e2e: non-lock failure exits failed without prompt parse/schema classification -ok 164 - lock preflight e2e: non-lock failure exits failed without prompt parse/schema classification - --- - duration_ms: 110.825704 - type: 'test' - ... -# Subtest: Scheduler Ratchet Logic (Log Checking) - # Subtest: excludes agents completed today (daily) - ok 1 - excludes agents completed today (daily) - --- - duration_ms: 2.989932 - type: 'test' - ... - # Subtest: excludes agents completed this week (weekly) - ok 2 - excludes agents completed this week (weekly) - --- - duration_ms: 0.423801 - type: 'test' - ... - # Subtest: combines locked and completed agents in exclusion list - ok 3 - combines locked and completed agents in exclusion list - --- - duration_ms: 0.603497 - type: 'test' - ... - # Subtest: ignores logs when --ignore-logs is set - ok 4 - ignores logs when --ignore-logs is set - --- - duration_ms: 3.24693 - type: 'test' - ... - # Subtest: handles missing log directory gracefully - ok 5 - handles missing log directory gracefully - --- - duration_ms: 0.678969 - type: 'test' - ... - 1..5 -ok 165 - Scheduler Ratchet Logic (Log Checking) - --- - duration_ms: 9.608756 - type: 'suite' - ... -# Subtest: Scheduler cycle ordering guarantees - # Subtest: keeps optional preflight before lock-acquire and preserves handoff/artifact/validation/complete ordering - ok 1 - keeps optional preflight before lock-acquire and preserves handoff/artifact/validation/complete ordering - --- - duration_ms: 11.495518 - type: 'test' - ... - # Subtest: includes lock backend failure metadata and classifier checkpoints - ok 2 - includes lock backend failure metadata and classifier checkpoints - --- - duration_ms: 2.6108 - type: 'test' - ... - 1..2 -ok 166 - Scheduler cycle ordering guarantees - --- - duration_ms: 14.820497 - type: 'suite' - ... -# Subtest: parseDateValue - # Subtest: parses a valid ISO string - ok 1 - parses a valid ISO string - --- - duration_ms: 1.07079 - type: 'test' - ... - # Subtest: returns null for empty input - ok 2 - returns null for empty input - --- - duration_ms: 0.211661 - type: 'test' - ... - # Subtest: returns null for invalid date strings - ok 3 - returns null for invalid date strings - --- - duration_ms: 0.175516 - type: 'test' - ... - 1..3 -ok 167 - parseDateValue - --- - duration_ms: 3.139781 - type: 'suite' - ... -# Subtest: isStrictSchedulerLogFilename - # Subtest: accepts valid completed log - ok 1 - accepts valid completed log - --- - duration_ms: 0.49591 - type: 'test' - ... - # Subtest: accepts valid failed log - ok 2 - accepts valid failed log - --- - duration_ms: 0.238961 - type: 'test' - ... - # Subtest: rejects deferred logs - ok 3 - rejects deferred logs - --- - duration_ms: 0.191435 - type: 'test' - ... - # Subtest: rejects filenames without timestamp - ok 4 - rejects filenames without timestamp - --- - duration_ms: 0.342076 - type: 'test' - ... - # Subtest: rejects plain text files - ok 5 - rejects plain text files - --- - duration_ms: 0.402723 - type: 'test' - ... - # Subtest: rejects .scheduler-run-state.json - ok 6 - rejects .scheduler-run-state.json - --- - duration_ms: 0.327144 - type: 'test' - ... - 1..6 -ok 168 - isStrictSchedulerLogFilename - --- - duration_ms: 2.870467 - type: 'suite' - ... -# Subtest: parseAgentFromFilename - # Subtest: extracts agent name from completed log - ok 1 - extracts agent name from completed log - --- - duration_ms: 1.751103 - type: 'test' - ... - # Subtest: extracts agent name from failed log - ok 2 - extracts agent name from failed log - --- - duration_ms: 0.253596 - type: 'test' - ... - # Subtest: returns null for invalid filename - ok 3 - returns null for invalid filename - --- - duration_ms: 0.130037 - type: 'test' - ... - 1..3 -ok 169 - parseAgentFromFilename - --- - duration_ms: 2.395436 - type: 'suite' - ... -# Subtest: parseTimestampFromFilename - # Subtest: parses timestamp from canonical filename - ok 1 - parses timestamp from canonical filename - --- - duration_ms: 0.441134 - type: 'test' - ... - # Subtest: returns null for non-canonical filename - ok 2 - returns null for non-canonical filename - --- - duration_ms: 0.15113 - type: 'test' - ... - 1..2 -ok 170 - parseTimestampFromFilename - --- - duration_ms: 0.712784 - type: 'suite' - ... -# Subtest: parseFrontmatterCreatedAt / parseFrontmatterAgent - # Subtest: reads created_at from frontmatter - ok 1 - reads created_at from frontmatter - --- - duration_ms: 0.431376 - type: 'test' - ... - # Subtest: reads agent from frontmatter - ok 2 - reads agent from frontmatter - --- - duration_ms: 0.612677 - type: 'test' - ... - # Subtest: returns null when no frontmatter - ok 3 - returns null when no frontmatter - --- - duration_ms: 0.171191 - type: 'test' - ... - 1..3 -ok 171 - parseFrontmatterCreatedAt / parseFrontmatterAgent - --- - duration_ms: 1.496664 - type: 'suite' - ... -# Subtest: buildRecentlyRunExclusionSet — scenarios - # Subtest: SCN-TWG-01: excludes agents with completed logs within 24-hour daily window - ok 1 - SCN-TWG-01: excludes agents with completed logs within 24-hour daily window - --- - duration_ms: 10.645209 - type: 'test' - ... - # Subtest: SCN-TWG-02: does NOT exclude agents whose last run is outside the 24-hour window - ok 2 - SCN-TWG-02: does NOT exclude agents whose last run is outside the 24-hour window - --- - duration_ms: 4.32373 - type: 'test' - ... - # Subtest: SCN-TWG-03: excludes agents with failed logs within the window - ok 3 - SCN-TWG-03: excludes agents with failed logs within the window - --- - duration_ms: 2.081152 - type: 'test' - ... - # Subtest: SCN-TWG-04: cross-midnight scenario — agent ran 10 min before midnight, checked 10 min after - ok 4 - SCN-TWG-04: cross-midnight scenario — agent ran 10 min before midnight, checked 10 min after - --- - duration_ms: 3.709189 - type: 'test' - ... - # Subtest: SCN-TWG-05: uses frontmatter created_at over filename timestamp when both present - ok 5 - SCN-TWG-05: uses frontmatter created_at over filename timestamp when both present - --- - duration_ms: 2.457024 - type: 'test' - ... - # Subtest: SCN-TWG-06: weekly window uses 7-day threshold - ok 6 - SCN-TWG-06: weekly window uses 7-day threshold - --- - duration_ms: 9.344418 - type: 'test' - ... - # Subtest: SCN-TWG-07: empty log directory returns empty set - ok 7 - SCN-TWG-07: empty log directory returns empty set - --- - duration_ms: 3.930174 - type: 'test' - ... - # Subtest: SCN-TWG-08: non-existent directory returns empty set without throwing - ok 8 - SCN-TWG-08: non-existent directory returns empty set without throwing - --- - duration_ms: 2.784625 - type: 'test' - ... - # Subtest: SCN-TWG-09: only the most recent log matters — agent excluded if any log is within window - ok 9 - SCN-TWG-09: only the most recent log matters — agent excluded if any log is within window - --- - duration_ms: 6.156334 - type: 'test' - ... - # Subtest: SCN-TWG-10: mixed roster — correctly partitions in-window vs out-of-window agents - ok 10 - SCN-TWG-10: mixed roster — correctly partitions in-window vs out-of-window agents - --- - duration_ms: 3.254561 - type: 'test' - ... - # Subtest: SCN-TWG-11: non-log files in directory are ignored - ok 11 - SCN-TWG-11: non-log files in directory are ignored - --- - duration_ms: 1.740459 - type: 'test' - ... - 1..11 -ok 172 - buildRecentlyRunExclusionSet — scenarios - --- - duration_ms: 77.537772 - type: 'suite' - ... -# Subtest: CADENCE_WINDOW_MS constants - # Subtest: daily window is exactly 24 hours - ok 1 - daily window is exactly 24 hours - --- - duration_ms: 0.333866 - type: 'test' - ... - # Subtest: weekly window is exactly 7 days - ok 2 - weekly window is exactly 7 days - --- - duration_ms: 0.115938 - type: 'test' - ... - 1..2 -ok 173 - CADENCE_WINDOW_MS constants - --- - duration_ms: 0.597872 - type: 'suite' - ... -# Subtest: secureRandom - # Subtest: returns values in [0, 1) - ok 1 - returns values in [0, 1) - --- - duration_ms: 2.767626 - type: 'test' - ... - # Subtest: returns varied values - ok 2 - returns varied values - --- - duration_ms: 0.403846 - type: 'test' - ... - 1..2 -ok 174 - secureRandom - --- - duration_ms: 4.945511 - type: 'suite' - ... -# Subtest: summarizes recent lock reliability by platform/cadence/backend/relay -ok 175 - summarizes recent lock reliability by platform/cadence/backend/relay - --- - duration_ms: 91.113986 - type: 'test' - ... -# Subtest: loadTorchConfig (Unit Tests with Mocked FS) - # Subtest: loads valid config correctly - ok 1 - loads valid config correctly - --- - duration_ms: 4.439481 - type: 'test' - ... - # Subtest: returns default config when file is missing - ok 2 - returns default config when file is missing - --- - duration_ms: 0.772277 - type: 'test' - ... - # Subtest: throws error on malformed JSON - ok 3 - throws error on malformed JSON - --- - duration_ms: 0.97533 - type: 'test' - ... - # Subtest: caches the config and ignores subsequent fs calls - ok 4 - caches the config and ignores subsequent fs calls - --- - duration_ms: 0.866245 - type: 'test' - ... - # Subtest: returns null for empty string lists (new consistent behavior) - ok 5 - returns null for empty string lists (new consistent behavior) - --- - duration_ms: 0.869315 - type: 'test' - ... - 1..5 -ok 176 - loadTorchConfig (Unit Tests with Mocked FS) - --- - duration_ms: 9.892753 - type: 'suite' - ... -# Subtest: torch-config - # Subtest: getTorchConfigPath - # Subtest: returns default path when env var is not set - ok 1 - returns default path when env var is not set - --- - duration_ms: 1.129402 - type: 'test' - ... - # Subtest: returns custom path when env var is set - ok 2 - returns custom path when env var is set - --- - duration_ms: 0.251105 - type: 'test' - ... - 1..2 - ok 1 - getTorchConfigPath - --- - duration_ms: 2.754813 - type: 'suite' - ... - # Subtest: parseTorchConfig - # Subtest: parses new lock backend knobs - ok 1 - parses new lock backend knobs - --- - duration_ms: 1.452746 - type: 'test' - ... - 1..1 - ok 2 - parseTorchConfig - --- - duration_ms: 1.885569 - type: 'suite' - ... - # Subtest: loadTorchConfig - # Subtest: throws fatal error for invalid relay URL - ok 1 - throws fatal error for invalid relay URL - --- - duration_ms: 16.214592 - type: 'test' - ... - # Subtest: throws fatal error for invalid timeout range - ok 2 - throws fatal error for invalid timeout range - --- - duration_ms: 2.552239 - type: 'test' - ... - # Subtest: throws error for malformed JSON - ok 3 - throws error for malformed JSON - --- - duration_ms: 1.793541 - type: 'test' - ... - 1..3 - ok 3 - loadTorchConfig - --- - duration_ms: 21.548194 - type: 'suite' - ... - # Subtest: backend getters - # Subtest: returns default relays - ok 1 - returns default relays - --- - duration_ms: 3.596335 - type: 'test' - ... - # Subtest: validates env knob values - ok 2 - validates env knob values - --- - duration_ms: 2.818353 - type: 'test' - ... - 1..2 - ok 4 - backend getters - --- - duration_ms: 6.918252 - type: 'suite' - ... - 1..4 -ok 177 - torch-config - --- - duration_ms: 34.249558 - type: 'suite' - ... -# Subtest: Async Utilities - # Subtest: withTimeout - # Subtest: resolves when promise completes before timeout - ok 1 - resolves when promise completes before timeout - --- - duration_ms: 11.598561 - type: 'test' - ... - # Subtest: rejects when promise times out - ok 2 - rejects when promise times out - --- - duration_ms: 11.20731 - type: 'test' - ... - # Subtest: clears timeout on success - ok 3 - clears timeout on success - --- - duration_ms: 0.552497 - type: 'test' - ... - # Subtest: propagates original promise rejection - ok 4 - propagates original promise rejection - --- - duration_ms: 0.972102 - type: 'test' - ... - 1..4 - ok 1 - withTimeout - --- - duration_ms: 25.978364 - type: 'suite' - ... - 1..1 -ok 178 - Async Utilities - --- - duration_ms: 26.713965 - type: 'suite' - ... -# (node:7671) ExperimentalWarning: The MockTimers API is an experimental feature and might change at any time -# (Use `node --trace-warnings ...` to show where the warning was created) -# Subtest: Date Utilities - # Subtest: todayDateStr - # Subtest: returns current date in YYYY-MM-DD format - ok 1 - returns current date in YYYY-MM-DD format - --- - duration_ms: 2.136332 - type: 'test' - ... - 1..1 - ok 1 - todayDateStr - --- - duration_ms: 3.328004 - type: 'suite' - ... - # Subtest: nowUnix - # Subtest: returns current unix timestamp - ok 1 - returns current unix timestamp - --- - duration_ms: 0.489641 - type: 'test' - ... - 1..1 - ok 2 - nowUnix - --- - duration_ms: 0.682926 - type: 'suite' - ... - # Subtest: getIsoWeekStr - # Subtest: returns correct ISO week for simple cases - ok 1 - returns correct ISO week for simple cases - --- - duration_ms: 0.469011 - type: 'test' - ... - # Subtest: handles year boundaries correctly - ok 2 - handles year boundaries correctly - --- - duration_ms: 0.209491 - type: 'test' - ... - # Subtest: handles leap years - ok 3 - handles leap years - --- - duration_ms: 0.34657 - type: 'test' - ... - # Subtest: handles empty input (defaults to today) - ok 4 - handles empty input (defaults to today) - --- - duration_ms: 0.448241 - type: 'test' - ... - # Subtest: handles invalid input gracefully - ok 5 - handles invalid input gracefully - --- - duration_ms: 1.941335 - type: 'test' - ... - 1..5 - ok 3 - getIsoWeekStr - --- - duration_ms: 3.937706 - type: 'suite' - ... - 1..3 -ok 179 - Date Utilities - --- - duration_ms: 9.029147 - type: 'suite' - ... -# Subtest: Relay Utilities - # Subtest: mergeRelayList - # Subtest: merges two lists of relays without duplicates - ok 1 - merges two lists of relays without duplicates - --- - duration_ms: 1.066875 - type: 'test' - ... - # Subtest: handles empty lists - ok 2 - handles empty lists - --- - duration_ms: 0.303041 - type: 'test' - ... - 1..2 - ok 1 - mergeRelayList - --- - duration_ms: 1.771103 - type: 'suite' - ... - 1..1 -ok 180 - Relay Utilities - --- - duration_ms: 1.993348 - type: 'suite' - ... -# Subtest: Platform Utilities - # Subtest: detectPlatform - # Subtest: detects jules - ok 1 - detects jules - --- - duration_ms: 0.537204 - type: 'test' - ... - # Subtest: detects codex - ok 2 - detects codex - --- - duration_ms: 0.156607 - type: 'test' - ... - # Subtest: detects goose - ok 3 - detects goose - --- - duration_ms: 0.151821 - type: 'test' - ... - # Subtest: detects claude via CLAUDE_API_KEY - ok 4 - detects claude via CLAUDE_API_KEY - --- - duration_ms: 0.122179 - type: 'test' - ... - # Subtest: detects claude via ANTHROPIC_API_KEY - ok 5 - detects claude via ANTHROPIC_API_KEY - --- - duration_ms: 0.175862 - type: 'test' - ... - # Subtest: detects gemini via GEMINI_API_KEY - ok 6 - detects gemini via GEMINI_API_KEY - --- - duration_ms: 0.147506 - type: 'test' - ... - # Subtest: detects gemini via GOOGLE_API_KEY - ok 7 - detects gemini via GOOGLE_API_KEY - --- - duration_ms: 0.126309 - type: 'test' - ... - # Subtest: detects antigravity via ANTIGRAVITY_API_KEY - ok 8 - detects antigravity via ANTIGRAVITY_API_KEY - --- - duration_ms: 0.120188 - type: 'test' - ... - # Subtest: detects antigravity via ANTIGRAVITY_SESSION_ID - ok 9 - detects antigravity via ANTIGRAVITY_SESSION_ID - --- - duration_ms: 0.153484 - type: 'test' - ... - # Subtest: detects qwen via QWEN_API_KEY - ok 10 - detects qwen via QWEN_API_KEY - --- - duration_ms: 0.167124 - type: 'test' - ... - # Subtest: returns null if no platform detected - ok 11 - returns null if no platform detected - --- - duration_ms: 0.150074 - type: 'test' - ... - 1..11 - ok 1 - detectPlatform - --- - duration_ms: 2.672234 - type: 'suite' - ... - 1..1 -ok 181 - Platform Utilities - --- - duration_ms: 2.805695 - type: 'suite' - ... -# Subtest: File Utilities - # Subtest: ensureDir - # Subtest: creates directory if it does not exist - ok 1 - creates directory if it does not exist - --- - duration_ms: 0.798332 - type: 'test' - ... - # Subtest: does nothing if directory already exists - ok 2 - does nothing if directory already exists - --- - duration_ms: 0.557473 - type: 'test' - ... - # Subtest: creates nested directories recursively - ok 3 - creates nested directories recursively - --- - duration_ms: 0.444448 - type: 'test' - ... - 1..3 - ok 1 - ensureDir - --- - duration_ms: 3.46587 - type: 'suite' - ... - 1..1 -ok 182 - File Utilities - --- - duration_ms: 3.717718 - type: 'suite' - ... -# Subtest: String Utilities - # Subtest: relayListLabel - # Subtest: joins relay URLs with comma and space - ok 1 - joins relay URLs with comma and space - --- - duration_ms: 0.211261 - type: 'test' - ... - # Subtest: handles single relay - ok 2 - handles single relay - --- - duration_ms: 0.108942 - type: 'test' - ... - # Subtest: handles empty array - ok 3 - handles empty array - --- - duration_ms: 0.095059 - type: 'test' - ... - 1..3 - ok 1 - relayListLabel - --- - duration_ms: 0.598629 - type: 'suite' - ... - 1..1 -ok 183 - String Utilities - --- - duration_ms: 0.737256 - type: 'suite' - ... -# Subtest: passes when artifacts include metadata and failure IDs map to unresolved known issue entries -ok 184 - passes when artifacts include metadata and failure IDs map to unresolved known issue entries - --- - duration_ms: 80.936797 - type: 'test' - ... -# Subtest: fails when required metadata is missing from artifacts -ok 185 - fails when required metadata is missing from artifacts - --- - duration_ms: 76.395834 - type: 'test' - ... -# Subtest: fails when failure identifiers are not cross-linked to known issues or incidents -ok 186 - fails when failure identifiers are not cross-linked to known issues or incidents - --- - duration_ms: 73.707205 - type: 'test' - ... -1..186 -# tests 372 -# suites 52 -# pass 371 -# fail 0 -# cancelled 0 -# skipped 1 -# todo 0 -# duration_ms 16058.014374 diff --git a/torch-config.example.json b/torch-config.example.json index df7ea44..dafd23d 100644 --- a/torch-config.example.json +++ b/torch-config.example.json @@ -18,13 +18,38 @@ }, "dashboard": { "defaultCadenceView": "daily", - "defaultStatusView": "active" + "defaultStatusView": "active", + "auth": "admin:password" }, "scheduler": { "firstPromptByCadence": { "daily": "scheduler-update-agent", "weekly": "bug-reproducer-agent" }, + "handoffCommandByCadence": { + "daily": "node torch/scripts/agent/run-selected-prompt.mjs", + "weekly": "node torch/scripts/agent/run-selected-prompt.mjs" + }, + "handoffPolicyByCadence": { + "daily": { + "maxAttempts": 2, + "retryBackoffMs": 1000, + "retryOnPatterns": [ + "stream disconnected", + "timeout", + "temporarily unavailable" + ], + "fallbackPlatform": "linux" + }, + "weekly": { + "maxAttempts": 2, + "retryBackoffMs": 1000 + } + }, + "runnerHealthCommandByCadence": { + "daily": "command -v codex >/dev/null 2>&1", + "weekly": "command -v codex >/dev/null 2>&1" + }, "memoryPolicyByCadence": { "daily": { "mode": "required", @@ -37,10 +62,12 @@ "MEMORY_STORED" ], "retrieveArtifacts": [ - ".scheduler-memory/latest/daily/retrieve.ok" + ".scheduler-memory/latest/daily/retrieve.ok", + ".scheduler-memory/latest/daily/retrieve.json" ], "storeArtifacts": [ - ".scheduler-memory/latest/daily/store.ok" + ".scheduler-memory/latest/daily/store.ok", + ".scheduler-memory/latest/daily/store.json" ] }, "weekly": { @@ -54,10 +81,12 @@ "MEMORY_STORED" ], "retrieveArtifacts": [ - ".scheduler-memory/latest/weekly/retrieve.ok" + ".scheduler-memory/latest/weekly/retrieve.ok", + ".scheduler-memory/latest/weekly/retrieve.json" ], "storeArtifacts": [ - ".scheduler-memory/latest/weekly/store.ok" + ".scheduler-memory/latest/weekly/store.ok", + ".scheduler-memory/latest/weekly/store.json" ] } }, diff --git a/torch-config.json b/torch-config.json index 60cda0f..96fe9b9 100644 --- a/torch-config.json +++ b/torch-config.json @@ -34,10 +34,12 @@ "MEMORY_STORED" ], "retrieveArtifacts": [ - ".scheduler-memory/latest/daily/retrieve.ok" + ".scheduler-memory/latest/daily/retrieve.ok", + ".scheduler-memory/latest/daily/retrieve.json" ], "storeArtifacts": [ - ".scheduler-memory/latest/daily/store.ok" + ".scheduler-memory/latest/daily/store.ok", + ".scheduler-memory/latest/daily/store.json" ] }, "weekly": { @@ -51,12 +53,14 @@ "MEMORY_STORED" ], "retrieveArtifacts": [ - ".scheduler-memory/latest/weekly/retrieve.ok" + ".scheduler-memory/latest/weekly/retrieve.ok", + ".scheduler-memory/latest/weekly/retrieve.json" ], "storeArtifacts": [ - ".scheduler-memory/latest/weekly/store.ok" + ".scheduler-memory/latest/weekly/store.ok", + ".scheduler-memory/latest/weekly/store.json" ] } } } -} \ No newline at end of file +}