Skip to content

Harness evolution: approved blueprint, phase plans, and full Phase 1-4 implementation - #42

Open
noodlemind wants to merge 14 commits into
mainfrom
claude/harness-evolution-proposals-tqgkw6
Open

Harness evolution: approved blueprint, phase plans, and full Phase 1-4 implementation#42
noodlemind wants to merge 14 commits into
mainfrom
claude/harness-evolution-proposals-tqgkw6

Conversation

@noodlemind

@noodlemind noodlemind commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Summary

Delivers the complete harness evolution cycle on one branch: the approved design blueprint, the phase plans, and the implementation of all four phases — built as three parallel workstreams and integrated with one commit each.

Design (approved)

  • knowledge/proposals/harness-evolution-blueprint.md — nine-proposal adaptation blueprint, three-lens adversarially reviewed, Human Decision: Approved 2026-08-06 with the §4/§5/§5a normative clauses as binding conditions.
  • docs/plans/2026-08-06-feat-harness-evolution-phase1-plan.md (live plan, now status: review, all ACs ticked) + docs/plans/harness-evolution-phase-drafts.md (Phases 2–4 scoping).

Implementation

feat: branch-aware two-layer knowledge store (Phases 1–2)

  • commit:/branch:/base: provenance stamped on episodes and learnings, preserved across every re-render; provenance excluded from the 1200-byte learning cap (no strike risk).
  • Events allow-list fix (init_repo/recall/validate_plan/index now record).
  • lib/git-context.mjs: deterministic <slug>-<8hex> branch keys, detached-HEAD detection, merge-base derivation.
  • Layered read path as one shared overlay (retrieve + eval): protected golden claims are never shadowed, governance binds both layers, ancestry-gated buckets, byte-identical with no buckets.
  • Buckets under branches/<branch-key>/ with per-bucket ledgers and schema marker; write routing derived from git context at write time, failing closed to branch-local; layer-aware absorb/purge/rebuild/mirror (§5a); knowledge status/promote/prune; promotion lane with hash-verified candidacy exemption, never-strike rejections, chunked --all, and the absorb-branch ledger action whose replay rule is regression-tested (retireabsorb-branchrebuild stays retired); doctor K5–K7; per-layer SLO attribution.

feat: optional tree-sitter structural index (Phase 3)

  • web-tree-sitter@0.25.10 + TS/JS/Python/Java grammar WASMs as Sonatype-vetted pinned optionalDependencies; sha256 grammars.lock verified on the same bytes instantiated; loud lexical fallback (doctor S1 fails); required runtime deps stay at 1.
  • Persistent index at ~/.harness/index/<repo-id>/structural/ (files/symbols/graph/meta), incremental via mtime+size → sha256, harness index --structural [--since <ref>] with validated refs; async parsing confined to the index command — orient stays sync and prefers current structural tables (byte-identical otherwise); secrets redacted at write, inertLine at render; three-audience output (ledger / bounded JSON envelope / ≤1000-token agent digest).

feat: per-check verify severity + structural-expectations (Phase 4)

  • Policy v2 checks: map (advisory/warn/enforce, v1 files byte-identical); advisory failures never flip the outcome and are preserved in evidence advisoryFailures.
  • structural-expectations check: unplanned symbol changes vs ## Impacted Files, removed exported symbols with surviving callers, optional plan structural_expectations: block; skips (never fails) on missing/stale index.
  • Integration seam test proves the real builder output flows through the shape reader into the check.

chore: registry candidate entries for the three shipped surfaces (per the approval conditions); docs/MEMORY-MODEL.md updated to describe the shipped layered store; tool-contract rows added additively with the events footnote corrected.

Verification

  • Full suite: 784 pass / 0 fail after merging origin/main (feat: report real Copilot billing and harness usage; retire the evaluation tree #38 eval-tree retirement absorbed; checks.yaml consistency confirmed). Baseline was 655 — 116 new tests from the three workstreams plus integration seam coverage.
  • harness validate-plan green on the Phase 1 plan; prompt-library-contracts 31/31 at every integration step; asset parity green.
  • Branch is synced with origin/main and MERGEABLE.

Summary by CodeRabbit

  • New Features
    • Added branch-aware knowledge storage with status, promotion, and pruning commands.
    • Added optional structural code indexing with incremental updates, JSON output, and lexical fallback.
    • Added structural expectation checks for detecting unplanned changes and removed symbols.
    • Added Git provenance to captured knowledge and orientation context.
  • Bug Fixes
    • Improved maintenance, routing, governance, and verification across golden and branch-local knowledge.
    • Added configurable check severities, including advisory and warning outcomes.
  • Documentation
    • Expanded CLI help, contracts, architecture guidance, and evolution plans.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e04679e4-4b52-4bce-a600-c8ef18aceb93

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Summary

The change adds branch-local knowledge layers, Git provenance, lifecycle commands, structural indexing, policy-v2 severities, structural verification, documentation, and integration tests.

Changes

Harness evolution

Layer / File(s) Summary
Contracts and plans
.github/..., docs/..., knowledge/..., packages/harness/README.md
Documents layered knowledge, lifecycle commands, structural indexing, policy severity, capabilities, and phased implementation.
CLI and Git context
packages/harness/bin/harness.mjs, packages/harness/lib/commands.mjs, packages/harness/lib/git-context.mjs, packages/harness/lib/orient.mjs
Adds structural indexing, knowledge lifecycle commands, Git context, provenance headers, lifecycle events, and layer flags.
Layered knowledge
packages/harness/lib/knowledge/*
Routes reads and writes across golden and branch buckets. Adds provenance, overlays, governance, promotion, pruning, maintenance, and layer-aware consolidation.
Structural index
packages/harness/lib/repo-map/*, packages/harness/lib/structural/shape.mjs
Adds persistent structural indexing, Tree-sitter extraction with lexical fallback, bounded graph data, integrity checks, incremental rebuilds, and structural repo-map ranking.
Structural verification
packages/harness/lib/policy.mjs, packages/harness/lib/structural/expectations.mjs, packages/harness/lib/verify.mjs
Adds policy-v2 severity mappings, structural expectations, advisory failures, and severity-aware verification outcomes.
Validation
packages/harness/test/*
Adds coverage for Git context, lifecycle events, layered knowledge, provenance, structural indexing, structural verification, policy compatibility, maintenance, and CLI behavior.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the approved blueprint, phase plans, and Phases 1–4 implementation described in the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/harness-evolution-proposals-tqgkw6

Comment @coderabbitai help to get the list of available commands.

Design documentation only — no CLI, store, or primitive behavior changes.
Maps nine externally supplied proposals (two-layer golden/branch-local
knowledge, deterministic retrieval, structural indexing, layer-aware
compounding, plan/verify enrichment, knowledge lifecycle commands,
worktree hardening, token budgeting, provenance governance) onto the
existing harness architecture, marking already-satisfied proposals as
verify-and-document and phasing the rest.

Two owner-fixed decisions are normative in the blueprint: branch-local
knowledge layers extend the existing external store at
~/.harness/knowledge/<repo-id>/ (never an in-repo .harness/knowledge/),
and structural parsing adopts web-tree-sitter as an optional WASM tier
behind the existing extract seam with the lexical extractor as the
permanent fallback.

The capability registry is deliberately untouched: the blueprint ships
with its Human Decision pending, and registry candidate entries are the
first post-approval step. Current-state docs gain only clearly labeled
planned-evolution pointers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0169cWqYhLedkYhL8hEyudSq
@noodlemind
noodlemind force-pushed the claude/harness-evolution-proposals-tqgkw6 branch from f809f7d to ef9aeff Compare August 6, 2026 02:44
claude added 4 commits August 6, 2026 02:46
Adds a section committing the blueprint's proposed command surfaces
(knowledge lifecycle, structural index) to be conforming citizens of the
separately planned CLI workbench/TUI track: registry-dispatchable,
versioned JSON envelope with JSONL streaming, cooperative cancellation
with distinct cancelled/timed-out outcomes, and a stable generation
stamp the workbench can render.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0169cWqYhLedkYhL8hEyudSq
…eprint

Blueprint section 9 previously required only a human rendering and the
workbench's JSON envelope for new command surfaces. This names the third
audience — the agent — as a first-class rendering: budgeted, deterministic,
injection-framed text following the existing context-pack/plan-slice/get
pattern, with the rule that agents consume the budgeted lane and envelope
JSON never enters model context. The structural query surface (P3) now
points to that contract for its agent rendering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0169cWqYhLedkYhL8hEyudSq
Merges the owner's tightened draft with fixes for the findings from the
three-lens adversarial review. Keeps the draft's improvements: repo-keyed
structural index at ~/.harness/index/<repo-id>/structural/, the
shadowed-claim promotion rule, branch rename handling, and the pinned
TS/JS+Python+Java grammar set.

Closes the review's design-soundness findings as normative clauses:
protected golden claims can never be shadowed on the read path and the
governance ledger binds both layers; promotion runs in an explicit lane
(per-bucket ledgers, hash-verified candidacy exemption, never-strike
rejections, chunked --all); the absorb-branch ledger action can never
become an id's standing decision; hand-edit absorption, purge, rebuild,
and mirroring get layer-aware semantics in section 5a; provenance fields
must be emitted by both serializers; default-branch determination fails
closed to branch-local; WASM grammars gain integrity verification;
Phase 1 is reduced to reads-only so it is internally consistent; the
events pre-work claim is corrected to hygiene. Restores section 9
(workbench relationship and the three-audience output contract).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0169cWqYhLedkYhL8hEyudSq
Records the Human Decision (approved 2026-08-06) in the evolution
blueprint with the section 4/5/5a normative clauses as binding
conditions. Adds the live Phase 1 plan (provenance stamps, events
allow-list fix, branch detection and key derivation, gated layered read
path, knowledge status) as a dated, schema-v1, gate-ready plan —
validated green by harness validate-plan — plus undated Phase 2-4
drafts that respect the one-live-plan rule and are promoted to dated
plans when their phase starts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0169cWqYhLedkYhL8hEyudSq
@noodlemind noodlemind changed the title Add local-first harness evolution blueprint (design docs only) Harness evolution: approved blueprint + phase implementation plans Aug 6, 2026
@noodlemind noodlemind changed the title Harness evolution: approved blueprint + phase implementation plans Harness evolution: approved blueprint, phase plans, and full Phase 1-4 implementation Aug 6, 2026
@noodlemind

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/harness/lib/knowledge/consolidate.mjs (1)

288-310: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Read consolidation aggregates from the routed layer

On a branch lane, consolidateStatus reads listLearnings(dir) while applyOps validates against layerRoot. Use listLearnings(view.layerRoot) for learnings, domains, and promotionCandidates so the status packet matches the bucket write target.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/knowledge/consolidate.mjs` around lines 288 - 310,
Update consolidateStatus to read learnings via listLearnings(view.layerRoot)
instead of listLearnings(dir), ensuring active counts, domainPressure, and
promotionCandidates reflect the routed layer and match applyOps’ bucket write
target.
🟡 Minor comments (16)
packages/harness/lib/flags.mjs-192-193 (1)

192-193: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject a missing --since value.

harness index --structural --since sets flags.since to undefined. cmdIndex then skips validateSinceRef() and performs a full index. Reject a missing value before assigning the flag.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/flags.mjs` around lines 192 - 193, Update the --since
argument handling in the flags parser so the separate `--since` form validates
that `argv[++i]` exists before assigning `flags.since`; reject the missing-value
case instead of storing undefined, while preserving the existing
`--since=<value>` behavior.
packages/harness/lib/flags.mjs-203-204 (1)

203-204: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Require a complete integer for --stale.

parseInt('30days', 10) returns 30. A malformed --stale value can therefore prune buckets by an unintended age. Validate the complete string, then require a safe positive integer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/flags.mjs` around lines 203 - 204, Update the
stale-value parsing used by the --stale branches to validate the entire input as
an integer rather than accepting parseInt prefixes such as “30days”, then
require the result to be a safe positive integer. Preserve the existing flag
assignment and error-label behavior in both --stale= and separate-argument
forms, using parsePositiveInt as the shared validation point.
packages/harness/lib/commands.mjs-373-400 (1)

373-400: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fail when a non-dry-run structural index does not persist.

buildStructuralIndex returns written: false when a contained write is refused. This command still writes a successful event, emits pass: true, and returns exit code 0. A caller can therefore treat an unavailable structural index as current.

Return a failed result when !flags.dryRun && !result.written. Keep dry-run successful.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/commands.mjs` around lines 373 - 400, Update the result
handling after buildStructuralIndex so a non-dry-run with result.written false
is treated as failed: emit a failed event and JSON summary with exitCode 1, and
return the corresponding failure result. Preserve successful behavior for
persisted indexes and keep dry-run executions successful even when written is
false.
knowledge/proposals/harness-evolution-blueprint.md-3-8 (1)

3-8: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the stale proposal status.

Lines 3-8 say that implementation is prohibited pending a Human Decision. Lines 407-416 record approval on August 6, 2026. This conflict can cause plan or review tooling to treat the shipped work as unauthorized.

Proposed fix
-Status: **proposal — pending Human Decision.**
-Design documentation only. Nothing in this document describes current behavior, and no
-CLI, store, or primitive change may be built from it until the `## Human Decision`
-section records an approval ...
+Status: **approved.**
+The `## Human Decision` section approved this blueprint on 2026-08-06. Its listed
+conditions remain binding on implementation phases.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@knowledge/proposals/harness-evolution-blueprint.md` around lines 3 - 8,
Update the proposal status and prohibition text at the document header to
reflect the approval recorded in the Human Decision section (August 6, 2026).
Remove the stale pending/unapproved implementation restriction while preserving
the document’s design-only scope and decision-handling references.
docs/MEMORY-MODEL.md-708-709 (1)

708-709: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align the documentation with the shipped implementation.

This PR ships Phase 3 structural indexing and Phase 4 per-check verification severity. The current statements describe both as planned or unshipped.

  • docs/MEMORY-MODEL.md#L708-L709: replace the unshipped statement with the current Phase 3 and Phase 4 status.
  • docs/architecture/engineer-harness.md#L226-L226: update the link annotation to identify the blueprint as an approved design with shipped behavior.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/MEMORY-MODEL.md` around lines 708 - 709, Update docs/MEMORY-MODEL.md
lines 708-709 to state that Phase 3 structural indexing and Phase 4 per-check
verification severity are shipped, replacing the unshipped-design wording.
Update the link annotation in docs/architecture/engineer-harness.md line 226 to
identify the blueprint as an approved design with shipped behavior.
packages/harness/lib/structural/shape.mjs-24-26 (1)

24-26: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Correct the documented extractorTier values.

Line 26 documents "extractorTier": "lexical|tree-sitter". The builder writes 'treesitter' without a hyphen. See sanitizeEntry in packages/harness/lib/repo-map/structural-index.mjs (line 164) and createTreesitterExtract (tier: 'treesitter'). This file is the stated integration contract, so a consumer that matches on the documented spelling will never match a real index.

📝 Proposed fix
-//                 "extractorTier": "lexical|tree-sitter", "grammarVersions": {} }
+//                 "extractorTier": "lexical|treesitter", "grammarVersions": {} }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/structural/shape.mjs` around lines 24 - 26, Update the
documented extractorTier values in the meta.json contract comment to use the
actual builder spelling “treesitter” instead of “tree-sitter”, matching
sanitizeEntry and createTreesitterExtract while preserving the lexical value.
packages/harness/lib/repo-map/treesitter-extractor.mjs-98-106 (1)

98-106: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Validate lock.runtime in loadGrammarsLock.

loadGrammarsLock only requires lock.grammars. Two consumers then dereference lock.runtime without a guard:

  • Line 165: check('runtime', lock.runtime) reads spec.package.
  • Line 430: findWasm(roots, lock.runtime.package, lock.runtime.file).

If grammars.lock is present but has no runtime entry, both sites throw TypeError. grammarStatus is synchronous and is called by doctor S1, so the throw escapes there. Validate the key once at the single parse point instead.

🛡️ Proposed fix
 export function loadGrammarsLock({ lockPath = DEFAULT_LOCK_PATH } = {}) {
   try {
     const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
     if (!lock || typeof lock !== 'object' || !lock.grammars) return null;
+    const runtime = lock.runtime;
+    if (!runtime || typeof runtime.package !== 'string' || typeof runtime.file !== 'string') return null;
     return lock;
   } catch {
     return null;
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/repo-map/treesitter-extractor.mjs` around lines 98 -
106, Update loadGrammarsLock to require a valid lock.runtime object alongside
lock.grammars before returning the parsed lock; otherwise return null. Keep the
validation at this single parse point so grammarStatus and findWasm can safely
use lock.runtime without additional guards.
packages/harness/lib/verify.mjs-240-253 (1)

240-253: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Handle skipped as neutral in verify output.

cmdVerify treats every status other than passed as a failure candidate. This displays a skipped check as a warning, includes it in failure counts, and can select it as the next fix target. Exclude skipped from these failure paths. The evidence JSON already preserves status; events.safeChecks does not.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/verify.mjs` around lines 240 - 253, Update cmdVerify’s
status handling so checks with status 'skipped' are neutral: exclude them from
warning output, failure counts, and next-fix-target selection while retaining
passed and actionable failure behavior. Preserve the existing status in evidence
JSON, and update events.safeChecks to retain each check’s status as well.
packages/harness/lib/knowledge/admin.mjs-1100-1115 (1)

1100-1115: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Include bucket learnings in the --yes preview count.

Lines 1107-1115 add bucket learnings to archived. The confirmation preview does not. Line 1050 computes archivedPreview from listLearnings(storePath) only, so the blocked message at Line 1056 under-reports how many learnings a rebuild resets once buckets exist. A human confirms a destructive reset against the wrong number.

🐛 Proposed fix for the preview count

Apply outside the selected range, at Line 1050:

-    const archivedPreview = fs.existsSync(storePath) ? listLearnings(storePath).length : 0;
+    const archivedPreview = fs.existsSync(storePath)
+      ? listLearnings(storePath).length +
+        listBuckets(storePath).reduce((n, b) => n + listLearnings(b.dir).length, 0)
+      : 0;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/knowledge/admin.mjs` around lines 1100 - 1115, Update
the archivedPreview calculation in the rebuild confirmation flow to include
learnings from every bucket returned by listBuckets(dir), matching the archived
count later computed from archivedLearnings plus archivedBranch. Keep the
existing preview message and confirmation behavior unchanged while ensuring
--yes reports the full destructive-reset count.
packages/harness/lib/knowledge/prune.mjs-51-53 (1)

51-53: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Reject malformed --stale values without parseInt coercion. parsePositiveInt rejects missing and fully invalid values, but it accepts 7junk as 7 and truncates 1.5 to 1. The latter changes the cutoff and can prune buckets newer than the requested age. Parse the complete value as a finite integer, and validate staleDays at the pruneBuckets boundary for direct callers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/knowledge/prune.mjs` around lines 51 - 53, Update
parsePositiveInt to reject trailing characters and fractional values by
requiring the complete input to be a finite integer, while preserving rejection
of missing or invalid values. Add equivalent staleDays validation at the
pruneBuckets boundary so direct callers cannot bypass parsing and use malformed
values.
packages/harness/lib/knowledge/overlay.mjs-99-112 (1)

99-112: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

A killed git merge-base reports "not an ancestor" instead of "unverifiable".

The documented contract states that false means verified not-an-ancestor, and null means unverifiable. spawnSync sets res.error only for spawn failures. If the 10 s timeout kills the process, res.error is undefined on some Node versions and res.status is null, so res.status === 0 ? true : false yields false.

loadLayeredLearnings then drops the whole bucket from the overlay on line 154, and knowledge status reports the bucket as ancestry-excluded. Treat a non-numeric exit status as unverifiable.

🐛 Proposed fix
     if (res.error) return null;
-    return res.status === 0 ? true : false;
+    // A timeout kill (signal set, status null) is UNVERIFIABLE, never a
+    // proven unrelated history.
+    if (typeof res.status !== 'number') return null;
+    return res.status === 0;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/knowledge/overlay.mjs` around lines 99 - 112, Update
bucketAncestryOk to treat any non-numeric res.status as unverifiable by
returning null, while preserving true for status 0 and false for numeric nonzero
statuses. Keep the existing res.error and exception handling unchanged.
packages/harness/lib/knowledge/layer.mjs-119-124 (1)

119-124: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

branchExists over-matches remote refs with slashes.

The remote test is r.startsWith('refs/remotes/') && r.endsWith('/' + branch). A ref such as refs/remotes/origin/release/main satisfies that test for branch === 'main'. branchExists then returns true for a branch that does not exist.

Two callers degrade silently. Doctor check K5 in packages/harness/lib/doctor.mjs stops reporting the bucket as an orphan. migrateRenamedBucket at line 144 stops treating the bucket as a rename candidate. Match the remote form exactly instead: one remote-name segment, then the full branch name.

🐛 Proposed fix for the remote-ref match
 export function branchExists(workspace, branch) {
   if (!branch) return null;
   const refs = listRefs(workspace);
   if (refs === null) return null;
-  return refs.some((r) => r === `refs/heads/${branch}` || (r.startsWith('refs/remotes/') && r.endsWith(`/${branch}`)));
+  return refs.some((r) => {
+    if (r === `refs/heads/${branch}`) return true;
+    if (!r.startsWith('refs/remotes/')) return false;
+    const rest = r.slice('refs/remotes/'.length);
+    const slash = rest.indexOf('/');
+    return slash > 0 && rest.slice(slash + 1) === branch;
+  });
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/knowledge/layer.mjs` around lines 119 - 124, Update
branchExists so the remote-ref check matches exactly one remote-name segment
followed by the full branch name, rather than any ref ending with /branch.
Preserve local refs/heads matching and null handling, while ensuring refs such
as refs/remotes/origin/release/main do not match branch "main".
packages/harness/lib/knowledge/overlay.mjs-189-199 (1)

189-199: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Preserve per-entry layer attribution.

The protected-shadow path returns two entries with the same id. cmdOrient stores layer data in an object keyed by id, so the branch entry overwrites the golden entry. harness report then uses that single value for its unique-ID layer split. Preserve both entries' layer identity in the event and report data.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/knowledge/overlay.mjs` around lines 189 - 199, Update
the protected-shadow handling in the overlay merge flow and the downstream
cmdOrient/report data structures so layer attribution is keyed by each unique
merged entry, not only by the shared base id. Preserve separate golden and
branch layer values for both entries, and ensure harness report uses those
per-entry identities when splitting layers.
packages/harness/test/structural-shape-compat.test.mjs-19-29 (1)

19-29: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Neutralize the ambient Git configuration in this fixture.

initRepo and commitAll run git without overriding GIT_CONFIG_GLOBAL and GIT_CONFIG_SYSTEM. Every other new suite in this PR sets both to /dev/null, for example packages/harness/test/structural-index.test.mjs lines 22-27.

A developer's global configuration therefore leaks into this fixture. If commit.gpgsign is true, commitAll fails or blocks on a passphrase prompt. If core.hooksPath is set globally, the fixture runs those hooks.

🛡️ Proposed fix
+const GIT_ENV = { ...process.env, GIT_CONFIG_GLOBAL: '/dev/null', GIT_CONFIG_SYSTEM: '/dev/null' };
+
 function initRepo(ws) {
   fs.mkdirSync(ws, { recursive: true });
-  execFileSync('git', ['init', '-q'], { cwd: ws });
-  execFileSync('git', ['config', 'user.email', 't@t'], { cwd: ws });
-  execFileSync('git', ['config', 'user.name', 't'], { cwd: ws });
+  execFileSync('git', ['init', '-q'], { cwd: ws, env: GIT_ENV });
+  execFileSync('git', ['config', 'user.email', 't@t'], { cwd: ws, env: GIT_ENV });
+  execFileSync('git', ['config', 'user.name', 't'], { cwd: ws, env: GIT_ENV });
 }
 
 function commitAll(ws, message) {
-  execFileSync('git', ['add', '-A'], { cwd: ws });
-  execFileSync('git', ['commit', '-qm', message], { cwd: ws });
+  execFileSync('git', ['add', '-A'], { cwd: ws, env: GIT_ENV });
+  execFileSync('git', ['commit', '-qm', message], { cwd: ws, env: GIT_ENV });
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/structural-shape-compat.test.mjs` around lines 19 - 29,
Update the git invocations in initRepo and commitAll to neutralize both global
and system Git configuration by setting GIT_CONFIG_GLOBAL and GIT_CONFIG_SYSTEM
to /dev/null, matching the environment setup used by the other structural test
suites. Ensure every git command in these fixture helpers inherits those
overrides.
packages/harness/test/structural-shape-compat.test.mjs-63-69 (1)

63-69: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Pass the impacted-file allowlist through plan.sections.impactedFiles.

parseImpactedFiles ignores plan.body, so this test also reports unplanned-symbol-change. Add the supported plan shape and assert that no unplanned-symbol-change finding exists.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/structural-shape-compat.test.mjs` around lines 63 - 69,
Update the test’s plan fixture to provide the impacted-file allowlist through
plan.sections.impactedFiles instead of plan.body, matching the shape consumed by
parseImpactedFiles. In the assertions for runStructuralExpectations, also verify
that result.findings contains no finding with type unplanned-symbol-change while
preserving the existing removed-symbol-with-callers assertion.
packages/harness/test/treesitter-extractor.test.mjs-181-196 (1)

181-196: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Resolve the runtime through Node module resolution

The hard-coded path checks only packages/harness/node_modules. If web-tree-sitter is hoisted, the test skips both integrity assertions and still passes. Use createRequire(import.meta.url).resolve('web-tree-sitter/tree-sitter.wasm') so the test covers hoisted installations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/treesitter-extractor.test.mjs` around lines 181 - 196,
Update the runtime source lookup in the test setup to resolve
web-tree-sitter/tree-sitter.wasm via Node module resolution using
createRequire(import.meta.url), rather than deriving a path from
DEFAULT_LOCK_PATH. Keep the existing conditional copy and integrity assertions
driven by the resolved runtime path so hoisted installations exercise the
corruption checks.
🧹 Nitpick comments (24)
packages/harness/lib/repo-map/index.mjs (1)

60-68: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename the inner structural score variable.

Line 66 declares const structural inside the map callback. Line 28 declares an outer structural that holds the loaded structural index. The two names describe unrelated values. The current code works, but a later edit that reads the index inside this callback would silently read the score instead.

♻️ Proposed rename
-    const structural = f.importedBy * 2 + Math.min(f.symbols.length, 12);
-    return { ...f, score: queryScore * 5 + structural };
+    const degreeScore = f.importedBy * 2 + Math.min(f.symbols.length, 12);
+    return { ...f, score: queryScore * 5 + degreeScore };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/repo-map/index.mjs` around lines 60 - 68, Rename the
inner structural score variable in the map callback that builds scored to a
distinct name, and update the score calculation to use it; leave the outer
structural index unchanged.
packages/harness/lib/policy.mjs (2)

67-70: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Guard checkSeverityFor against prototype-inherited ids.

checkSeverities is a plain object. policy?.checkSeverities?.[id] therefore resolves ids such as constructor, toString, and valueOf to Object.prototype members instead of falling through to defaultSeverity. Check ids reach this function from plan frontmatter (verification.required in runVerify), so a check named constructor yields a function as its severity. The outcome still degrades to gating in resolveOutcome, so this is not a bypass, but the recorded severity is wrong.

🛡️ Proposed fix
 export function checkSeverityFor(policy, id, defaultSeverity = 'enforce') {
-  return policy?.checkSeverities?.[id] ?? defaultSeverity;
+  const configured = policy?.checkSeverities;
+  return configured && Object.hasOwn(configured, id) ? configured[id] : defaultSeverity;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/policy.mjs` around lines 67 - 70, Update
checkSeverityFor to accept only own properties of policy.checkSeverities when
resolving id; inherited Object.prototype names such as constructor, toString,
and valueOf must fall through to defaultSeverity. Preserve the existing optional
handling and configured own-property severities.

49-63: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

checks: is honored under version: 1.

Line 49 accepts version 1 or 2. Line 63 then calls parseCheckSeverities for every policy, including a v1 policy. The comment at lines 7-10 presents checks: as a v2 schema addition. A repository that declares version: 1 and adds checks: silently gets v2 severity behavior, so the version field stops describing the active schema.

Decide one behavior and make it explicit: either ignore checks: under v1, or document that checks: is version-independent and adjust the comment at lines 7-10.

♻️ Option: gate on the declared version
-    checkSeverities: parseCheckSeverities(policy, policyPath),
+    checkSeverities: policy.version === 2 ? parseCheckSeverities(policy, policyPath) : {},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/policy.mjs` around lines 49 - 63, Update the policy
construction around parseCheckSeverities so checks: is only applied when the
declared policy version supports the v2 schema; for version 1, ignore checks and
preserve the existing default severity behavior. Keep version validation for
versions 1 and 2 unchanged, and make the version-gated behavior explicit in the
returned checkSeverities value.
packages/harness/lib/knowledge/store.mjs (1)

492-501: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

PROVENANCE_SHA_RE rejects SHA-256 object ids.

Line 492 accepts exactly 40 hex characters. Git repositories initialized with --object-format=sha256 produce 64-character object ids. On such a repository, provenanceLines drops commit: and base: silently, so learnings written there carry branch provenance but no commit or base. provenanceBytes stays consistent with that, so the byte-cap math does not break — only the recorded provenance is lost.

Accept both widths if you intend to support those repositories.

♻️ Proposed change
-const PROVENANCE_SHA_RE = /^[0-9a-f]{40}$/;
+// Both git object formats: 40-hex (SHA-1) and 64-hex (SHA-256, git >= 2.29).
+const PROVENANCE_SHA_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/knowledge/store.mjs` around lines 492 - 501, Update
PROVENANCE_SHA_RE and the validation used by provenanceLines to accept both
40-character SHA-1 and 64-character SHA-256 hexadecimal object IDs, preserving
the existing commit: and base: provenance output and provenanceBytes
consistency.
packages/harness/lib/knowledge/apply.mjs (2)

683-694: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Consider binding the digest over the envelope, not only parsed.ops.

Line 683 hashes JSON.stringify(parsed.ops) alone. promotion.branchKey sits outside that binding, so a hand-edited op-set can repoint the source bucket while the digest still validates. The per-op src.sha256 check at Line 1036 limits the damage — a swapped bucket only applies when it holds a byte-identical source learning — so this is hardening, not an exploit path.

If you widen the binding, promotionDigest in promote.mjs must hash the same shape.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/knowledge/apply.mjs` around lines 683 - 694, Bind the
promotion digest to the complete relevant envelope, including
promotion.branchKey, instead of hashing parsed.ops alone. Update the digest
verification near the apply flow and the promotionDigest implementation in
promote.mjs to serialize and hash the identical shape, preserving rejection on
mismatches.

721-737: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Derive the git context once and reuse it for both provenance and routing.

Line 727 calls deriveGitContext, and Line 736 calls resolveWriteLayer, which calls deriveGitContext again internally (layer.mjs Line 28). This spawns the git plumbing twice per apply run. It also lets the two reads disagree: if HEAD moves between them, the stamped writeProvenance names one commit while the layer routes from another.

resolveWriteLayer already returns context. Resolve routing first, then build writeProvenance from routing.context.

♻️ Proposed refactor
-  const gitContext = deriveGitContext({ workspace, home });
-  const writeProvenance = { commit: gitContext.headSha, branch: gitContext.branch, base: gitContext.baseSha };
-
-  // Write-layer routing (blueprint P4): ...
-  const routing = resolveWriteLayer({ workspace, home, layerOverride: layer === 'golden' ? 'golden' : null, log });
+  // Write-layer routing (blueprint P4): ...
+  const routing = resolveWriteLayer({ workspace, home, layerOverride: layer === 'golden' ? 'golden' : null, log });
+  // One HEAD read backs BOTH the stamp and the routing decision, so a claim's
+  // recorded commit can never name a different HEAD than the layer it landed in.
+  const writeProvenance = {
+    commit: routing.context.headSha,
+    branch: routing.context.branch,
+    base: routing.context.baseSha,
+  };

Drop the now-unused deriveGitContext import if nothing else in this module uses it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/knowledge/apply.mjs` around lines 721 - 737, Update the
apply flow around resolveWriteLayer to resolve routing first, then build
writeProvenance from routing.context so provenance and layer selection share one
git-context read. Remove the direct deriveGitContext call and its import from
this module if no other references remain, while preserving the existing routing
options and provenance fields.
packages/harness/lib/knowledge/layer.mjs (1)

151-162: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

A failed meta rewrite leaves the bucket moved with a stale meta.branch.

fs.renameSync runs first. If the following fs.writeFileSync throws, the catch returns null, so the caller records no migration. The directory has already moved to the new key, and its meta.json still names the old branch.

That state is self-inconsistent for the readers of meta.branch: doctor K6 reports a misroute, doctor K5 evaluates the wrong branch, and bucketAncestryOk still reads the old base. Write the updated meta.json into the source directory before the rename, or restore the original name when the rewrite fails.

♻️ Proposed ordering change
   try {
-    fs.renameSync(source.dir, target);
-    const meta = readBucketMeta(target) || {};
-    fs.writeFileSync(
-      path.join(target, 'meta.json'),
-      JSON.stringify({ ...meta, branch: context.branch, branchKey: context.branchKey }) + '\n',
-      'utf8'
-    );
+    const meta = readBucketMeta(source.dir) || {};
+    fs.writeFileSync(
+      path.join(source.dir, 'meta.json'),
+      JSON.stringify({ ...meta, branch: context.branch, branchKey: context.branchKey }) + '\n',
+      'utf8'
+    );
+    fs.renameSync(source.dir, target);
     return { migrated: true, from: source.key, to: context.branchKey };
   } catch {
     return null;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/knowledge/layer.mjs` around lines 151 - 162, Update the
migration flow around the rename and metadata write so the new branch and
branchKey metadata is persisted before moving the directory, or restore the
original directory name if that write fails. Ensure a failed migration cannot
leave the bucket at the target path with stale meta.branch data, while
preserving the existing success result and null failure behavior.
packages/harness/lib/doctor.mjs (1)

425-433: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

K5 spawns git show-ref once per bucket.

branchExists in packages/harness/lib/knowledge/layer.mjs calls listRefs, which runs git show-ref on every invocation with no memoization. This loop calls it once per bucket. With many buckets, harness doctor pays N process spawns, each carrying a 10 s timeout.

The ref list does not change during the check. Read it once and test each bucket branch against that list.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/doctor.mjs` around lines 425 - 433, Update the
orphan-check loop in the doctor flow to read the workspace’s Git refs once
before iterating buckets, then test each bucket’s branch against that cached ref
list instead of calling branchExists per bucket. Reuse the existing ref-list and
branch-matching helpers from knowledge/layer.mjs where applicable, while
preserving the current orphan detection behavior.
packages/harness/lib/knowledge/consolidate.mjs (1)

247-259: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Guard bucketKey before building the bucket path.

layerView selects the branch layer from routing?.layer === 'branch' alone. If resolveWriteLayer ever returns layer: 'branch' with a null bucketKey, bucketDirFor(dir, null) calls path.join with a null segment and throws a TypeError inside consolidateStatus. consolidateStatus is a read surface called from runOrient and doctor.mjs, so a throw there is not local. The write path in packages/harness/lib/knowledge/apply.mjs already pairs both conditions (routing.layer === 'branch' && routing.bucketKey). Mirror that pairing here.

🛡️ Proposed defensive fix
-  const layer = routing?.layer === 'branch' ? 'branch' : 'golden';
-  const bucketKey = layer === 'branch' ? routing.bucketKey : null;
+  const layer = routing?.layer === 'branch' && routing.bucketKey ? 'branch' : 'golden';
+  const bucketKey = layer === 'branch' ? routing.bucketKey : null;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/lib/knowledge/consolidate.mjs` around lines 247 - 259,
Update layerView so it selects the branch layer only when routing.layer is
'branch' and routing.bucketKey is present, mirroring the paired condition used
by the write path. Ensure bucketDirFor is never called with a null bucketKey,
while preserving the golden-layer fallback when either condition is absent.
packages/harness/test/store-migration.test.mjs (1)

58-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fail loudly instead of guessing 'main'.

The docblock states that the pin exists so writes route GOLDEN. If symbolic-ref --short HEAD returns nothing, line 59 pins the literal 'main'. When the workspace branch is not main, the pinned default no longer matches, layer routing falls back to branch-local, and the four call sites lose the precondition they were added to establish — without any test failure.

Assert that the branch resolved.

♻️ Proposed refactor
 function pinDefaultBranch(c) {
-  const branch = git(c.ws, ['symbolic-ref', '--short', 'HEAD']).stdout.trim() || 'main';
+  const branch = git(c.ws, ['symbolic-ref', '--short', 'HEAD']).stdout.trim();
+  assert.ok(branch, 'fixture workspace must have a resolvable branch for the GOLDEN-routing pin');
   const { dir } = ensureStore(c.ws, { home: c.harnessHome });
   fs.writeFileSync(path.join(dir, 'config.json'), JSON.stringify({ mode: 'on', commit: 'none', defaultBranch: branch }) + '\n');
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/store-migration.test.mjs` around lines 58 - 62, Update
pinDefaultBranch to fail when symbolic-ref --short HEAD resolves to an empty
value instead of defaulting to 'main'; assert that branch is present before
writing config.json, while preserving the existing resolved branch in
defaultBranch.
packages/harness/test/doctor-structural.test.mjs (1)

58-70: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Register temp-directory cleanup with t.after.

Each test calls fs.rmSync at the end of the test body. If an assertion throws, the workspace and home directories stay in os.tmpdir(). withHome already uses t.after in this file. Use the same mechanism for the directories.

♻️ Proposed refactor
-function withHome(t, home) {
+function withHome(t, home) {
   const saved = process.env.HARNESS_HOME;
   process.env.HARNESS_HOME = home;
   t.after(() => {
     if (saved === undefined) delete process.env.HARNESS_HOME;
     else process.env.HARNESS_HOME = saved;
   });
 }
+
+function tempTree(t, prefix) {
+  const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
+  t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
+  return dir;
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/doctor-structural.test.mjs` around lines 58 - 70,
Update the test using structuralChecks to register cleanup for ws and home with
t.after immediately after creating them, matching the existing withHome cleanup
pattern. Remove the direct fs.rmSync calls from the test body so cleanup runs
even when an assertion fails.
packages/harness/test/structural-index.test.mjs (2)

88-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Note the two different readStructuralIndex shapes.

This suite imports readStructuralIndex from lib/repo-map/structural-index.mjs and reads index.symbols as an object keyed by symbol name (line 92). packages/harness/test/structural-shape-compat.test.mjs line 53 and packages/harness/test/structural-expectations.test.mjs line 321 import a function with the identical name from lib/structural/shape.mjs and read index.symbols as an array of rows.

Both are correct today, and structural-shape-compat.test.mjs exists to bridge them. The identical export name across two modules with divergent return shapes is still easy to misread. Consider aliasing the import in one of the suites, for example readCompactIndex, to make the distinction visible at the call site.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/structural-index.test.mjs` around lines 88 - 92, Alias
the readStructuralIndex import from lib/repo-map/structural-index.mjs in
structural-index.test.mjs to a name such as readCompactIndex, and update its
call sites accordingly. Leave the separate lib/structural/shape.mjs import and
its array-shaped contract unchanged.

331-336: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Security Misconfiguration (CWE-693)

Reachability: Internal · Exploitability: Theoretical

Reachability path
● Entry
  packages/harness/test/index-structural-cli.test.mjs
│
▼
● Hop
  packages/harness/lib/repo-map/structural-index.mjs:47
  git
│
▼
● Hop
  packages/harness/test/doctor-structural.test.mjs
│
▼
● Hop
  packages/harness/lib/doctor.mjs:537
  head
│
▼
● Hop
  packages/harness/test/provenance.test.mjs
│
▼
● Hop
  packages/harness/lib/knowledge/apply.mjs:816
  recordContentFailure: Promotion rejections NEVER record quarantine strikes (blueprint §5):
│
▼
● Sink
  packages/harness/test/structural-index.test.mjs

Tighten the network-guard pattern and document its scope.

Match both quote styles for node:http and node:https. This source-text scan checks only six files, matches comments and strings, and does not follow transitive imports. It does not prove that the read path is network-free.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/structural-index.test.mjs` around lines 331 - 336,
Update the network-guard regex in the structural-index test to match both
single- and double-quoted node:http and node:https imports, and document that
the scan covers only six files, includes comments and strings, and does not
inspect transitive imports; do not describe it as proving the entire read path
is network-free.
packages/harness/test/provenance.test.mjs (2)

208-215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive the episode-block size instead of the magic 150.

Line 214 adds a hard-coded 150 as slack for the appended episode block. The constant has no derivation in the test, and the slack makes the assertion pass for a wide range of byte counts. The assertion then no longer proves that claim bytes, not provenance bytes, caused the rejection.

Measure the delta directly instead: capture bytes and provenanceBytes before the STRENGTHEN, then assert that the claim-byte growth alone crossed the cap.

♻️ Proposed refactor
+  const beforeStrengthen = listLearnings(dir2).find((l) => l.id === 'sql/probe');
+  const claimBytesBefore = beforeStrengthen.bytes - provenanceBytes(beforeStrengthen.fm);
   if (strengthened.exitCode !== 0) {
     assert.equal(strengthened.rejected[0].code, 'E_BYTE_CAP');
     const after = listLearnings(dir2).find((l) => l.id === 'sql/probe');
-    assert.ok(after.bytes - provenanceBytes(after.fm) + 150 > LEARNING_BYTE_CAP, 'rejection driven by claim bytes');
+    const claimBytesAfter = after.bytes - provenanceBytes(after.fm);
+    assert.ok(claimBytesAfter >= claimBytesBefore, 'claim bytes never shrink across a strengthen');
+    assert.ok(claimBytesBefore <= LEARNING_BYTE_CAP, 'the pre-strengthen claim body was under the cap');
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/provenance.test.mjs` around lines 208 - 215, Replace
the hard-coded 150 in the strengthened rejection assertion with a measured
episode-block delta: capture the learning’s bytes and provenanceBytes before the
STRENGTHEN, then compare the post-strengthening claim-byte total against the cap
using only the observed claim-byte growth. Keep the E_BYTE_CAP check and ensure
the assertion demonstrates rejection from claim bytes rather than provenance
bytes.

23-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Temp directories leak when an assertion throws. Both suites create workspaces and homes with fs.mkdtempSync and remove them with trailing statements in the test body, or not at all. A failing assertion skips the removal, so every failed run leaves Git repositories and knowledge stores in os.tmpdir(). The shared fix is to bind removal to the test context with t.after, which packages/harness/test/structural-index.test.mjs lines 282-287 already does correctly.

  • packages/harness/test/provenance.test.mjs#L23-L23: extend the tempDir helper to accept the test context and register t.after(() => fs.rmSync(dir, { recursive: true, force: true })), then thread the context through gitWorkspace and every tempDir call site. This suite currently removes nothing at all.
  • packages/harness/test/doctor-structural.test.mjs#L58-L70: replace the trailing fs.rmSync pairs in each of the five tests with a t.after-registered helper, matching the pattern withHome already uses in the same file.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/provenance.test.mjs` at line 23, The
temporary-directory cleanup must run even when tests fail. In
packages/harness/test/provenance.test.mjs at lines 23-23, update tempDir to
accept the test context and register t.after cleanup, then pass that context
through gitWorkspace and every tempDir call site. In
packages/harness/test/doctor-structural.test.mjs at lines 58-70, replace each of
the five tests’ trailing fs.rmSync cleanup pairs with t.after-registered cleanup
matching the existing withHome pattern.
packages/harness/test/structural-expectations.test.mjs (1)

543-571: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering the enforcement-mode and per-check-severity precedence.

These two tests pin per-check severity under enforcement: enforce. loadPolicy also accepts observe and warn as the global mode. The precedence when a global observe mode meets a per-check severity: enforce is not covered here, and that combination is the one most likely to surprise an operator.

Add a case that sets enforcement: observe with checks.structural-expectations.severity: enforce and pins the resulting outcome and exit code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/structural-expectations.test.mjs` around lines 543 -
571, Add a structural verification test alongside the existing severity tests
using global enforcement observe and structural-expectations severity enforce,
then assert the resulting outcome and enforcementExitCode to pin the precedence
behavior. Reuse structuralWorkspace, writeStructuralIndex, verifyFlags, and the
existing structural check assertions; keep the test focused on this
observe-versus-enforce combination.
packages/harness/test/layered-overlay.test.mjs (2)

145-146: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the no-op path.join(bucketDir, '') alias.

path.join(bucketDir, '') normalizes to bucketDir. bucketDir2 is therefore the same directory as bucketDir. The indirection suggests a second, different bucket and misleads the reader. Write directly to bucketDir.

♻️ Proposed change
   writeLearning(dir, 'sql/human', { trigger: 'human trigger', body: 'Human golden.', source: 'human' });
-  const bucketDir2 = path.join(bucketDir, '');
-  writeLearning(bucketDir2, 'sql/human', { trigger: 'human trigger', body: 'Branch challenger.' });
+  writeLearning(bucketDir, 'sql/human', { trigger: 'human trigger', body: 'Branch challenger.' });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/layered-overlay.test.mjs` around lines 145 - 146,
Remove the redundant bucketDir2 alias and update the writeLearning call in the
layered overlay test to pass bucketDir directly, preserving the existing
'sql/human' path and payload.

79-93: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The test title claims "no git calls" but the test does not verify that.

The title states "no layer fields, no git calls". The test asserts the ranked output only. It never asserts that no Git process is spawned. The second half also creates an empty branches/ directory. loadLayeredLearnings returns early only when branches/ does not exist, so with the directory present it calls deriveGitContext, which spawns Git. The zero-spawn fast path is therefore not covered.

Either drop "no git calls" from the title, or add an explicit assertion for the fast path. The blueprint treats the zero-spawn property as a requirement, so an assertion is preferable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/layered-overlay.test.mjs` around lines 79 - 93, The
test around rankLearnings must verify the title’s no-Git-calls guarantee.
Instrument or stub the Git process-spawn path, assert zero invocations for the
missing branches/ fast path, and keep the existing byte-identical output
assertions; ensure the empty branches/ case is tested separately or rename the
title so it does not incorrectly claim zero spawns.
packages/harness/test/layer-routing.test.mjs (3)

239-239: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert the specific error on the write path.

assert.throws without a predicate passes for any thrown error. If applyOps later fails for an unrelated reason, for example a rejected ops path, this assertion still passes and the schema guard on the write path goes untested. Reuse the same predicate applied at lines 236-238.

♻️ Proposed change
-  assert.throws(() => applyOps({ workspace: ws, opsPath: writeOps(ws, [addOp(ws, { slug: 'nope' })]), home }));
+  assert.throws(
+    () => applyOps({ workspace: ws, opsPath: writeOps(ws, [addOp(ws, { slug: 'nope' })]), home }),
+    (err) => err.code === 'E_STORE_SCHEMA'
+  );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/layer-routing.test.mjs` at line 239, Update the
applyOps assertion in the write-path test to pass the same error predicate used
by the assertion at lines 236-238, ensuring it validates the expected
schema-guard error rather than any thrown error.

242-263: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add the ambiguous-rename counter-case.

The test title states that migration happens "when unambiguous". The test only covers the unambiguous path. migrateRenamedBucket moves bucket contents on disk. If the guard for the ambiguous case regresses, two branch buckets can merge and no test detects it. Add a case with two candidate orphan buckets and assert that migrateRenamedBucket returns { migrated: false } and leaves both bucket directories in place.

Do you want me to generate that test case?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/layer-routing.test.mjs` around lines 242 - 263, Add an
ambiguous rename test alongside the existing migration test, creating two
candidate orphan bucket directories for the workspace before calling
migrateRenamedBucket. Assert it returns { migrated: false } and verify both
bucket directories remain present, ensuring ambiguous candidates are not merged
or moved.

303-313: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use a static import for consolidate.mjs.

Every other harness module in this file uses a static import at the top. The dynamic import provides no isolation, because the ES module registry caches the module for the process lifetime. It also forces the test callback to be async without need. Move the import to the top of the file.

♻️ Proposed change
 import { runDoctor } from '../lib/doctor.mjs';
+import { consolidateStatus } from '../lib/knowledge/consolidate.mjs';
 import { writeSession } from '../lib/session.mjs';
-test('listBuckets sees a routed bucket and consolidate status reports the branch lane', async () => {
+test('listBuckets sees a routed bucket and consolidate status reports the branch lane', () => {
-  const { consolidateStatus } = await import('../lib/knowledge/consolidate.mjs');
   const status = consolidateStatus({ workspace: ws, home, copilotHome: tempDir('route-ch2-') });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/layer-routing.test.mjs` around lines 303 - 313, Replace
the dynamic consolidate.mjs import inside the test with a static top-level
import, following the existing import style in the file. Update the test
callback to remove unnecessary async usage while preserving its
consolidateStatus call and assertions.
packages/harness/test/layer-maintenance.test.mjs (1)

79-80: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Route the fixture commits through the config-neutralizing git() helper.

The local git() helper at lines 16-22 sets GIT_CONFIG_GLOBAL=/dev/null and GIT_CONFIG_SYSTEM=/dev/null. These fixture commits call spawnSync('git', ...) directly, so they inherit the developer's global Git configuration. A global commit.gpgsign=true, a global hooks path, or a global core.autocrlf setting changes the outcome of the commit. The commit then fails silently, because the return status is not checked, and the purge under test runs against a different tree state than intended.

The same pattern repeats at lines 110-111, 134-135, and 153-154 in this file. Add an identity-carrying variant of the helper and use it at all four sites.

♻️ Proposed refactor
 function git(cwd, args) {
   return spawnSync('git', args, {
     cwd,
     encoding: 'utf8',
     env: { ...process.env, GIT_CONFIG_GLOBAL: '/dev/null', GIT_CONFIG_SYSTEM: '/dev/null' },
   });
 }
+
+/** Commit the store fixture with a fixed identity and no ambient git config. */
+function commitFixture(dir, message = 'fixture') {
+  git(dir, ['add', '-A']);
+  const res = git(dir, ['-c', 'user.name=t', '-c', 'user.email=t@t', 'commit', '-qm', message]);
+  assert.equal(res.status, 0, res.stderr);
+}
-  spawnSync('git', ['add', '-A'], { cwd: dir });
-  spawnSync('git', ['-c', 'user.name=t', '-c', 'user.email=t@t', 'commit', '-qm', 'fixture'], { cwd: dir });
+  commitFixture(dir);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/layer-maintenance.test.mjs` around lines 79 - 80,
Update the local git helper used by the fixture setup to provide an
identity-carrying variant that preserves its config-neutralizing environment
while adding the temporary user.name and user.email settings. Replace the direct
add/commit spawnSync calls at all four fixture-commit sites with this helper,
ensuring commit results are handled consistently with the existing helper
behavior.
packages/harness/test/knowledge-promote.test.mjs (1)

225-233: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Derive all chunk expectations from MAX_OPS_PER_RUN

Import MAX_OPS_PER_RUN from consolidate.mjs; promote.mjs does not export it. Derive the seed count, count assertions, and expected ID lists from the constant so changes to the limit do not make this test stale.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/knowledge-promote.test.mjs` around lines 225 - 233,
Update the test case around buildPromotionOps to import MAX_OPS_PER_RUN from
consolidate.mjs, then derive the seeded chunk count, emitted and remaining
assertions, and expected ID lists from that constant instead of hard-coded
values. Do not import the constant from promote.mjs, which does not export it.
packages/harness/test/knowledge-status.test.mjs (1)

119-127: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Pass an explicit copilotHome to exercise index status.

Without copilotHome, indexStatus throws while resolving its path, and knowledgeStatus silently sets drift to null. Use a temporary directory so this test covers the read-only index probe.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/test/knowledge-status.test.mjs` around lines 119 - 127,
Update the knowledgeStatus call in the read-only test to pass the temporary home
directory explicitly as copilotHome, while preserving the existing workspace and
home arguments. Ensure the test exercises indexStatus successfully and continues
verifying that status does not materialize the store.

Comment thread packages/harness/lib/commands.mjs
Comment thread packages/harness/lib/context-pack.mjs Outdated
Comment thread packages/harness/lib/knowledge/admin.mjs
Comment thread packages/harness/lib/knowledge/apply.mjs
Comment thread packages/harness/lib/knowledge/promote.mjs
Comment thread packages/harness/lib/repo-map/structural-index.mjs Outdated
Comment thread packages/harness/lib/structural/expectations.mjs Outdated
Comment thread packages/harness/lib/structural/expectations.mjs
Comment thread packages/harness/lib/structural/shape.mjs Outdated
Comment thread packages/harness/test/structural-shape-compat.test.mjs Outdated
@noodlemind
noodlemind force-pushed the claude/harness-evolution-proposals-tqgkw6 branch from 00dcfcf to 6483f03 Compare August 6, 2026 10:15
@linear-code
linear-code Bot marked this pull request as ready for review August 6, 2026 10:16
@noodlemind

Copy link
Copy Markdown
Owner Author

All 29 review findings are addressed in 6483f03 (1 Critical, 12 Major inline — each answered and resolved on its thread — plus the outside-diff major and the 16 minors):

  • Outside-diff: consolidateStatus now derives learnings/domains/promotionCandidates from the routed layerRoot, matching the write path.
  • Minors: strict --since/--stale value validation (both flag parse and pruneBuckets boundary); non-persisted structural build now fails loudly instead of exiting 0; blueprint status header aligned with its recorded approval; MEMORY-MODEL aligned with shipped Phases 3–4; extractorTier doc corrected; grammars.lock runtime shape validated; skipped checks neutral in verify output; rebuild --yes preview counts bucket learnings; timeout-killed merge-base reports unverifiable (null) rather than not-an-ancestor; branchExists matches exact remote ref paths; test hardening (ambient git config neutralized, hoist-safe runtime resolution, grammar-availability skip, supported plan shape).

Note on history: two interim commits addressing this review were replaced by the single verified commit 6483f03 (same fixes independently verified, one fix reworked to gate per-file on the recorded extractor tier rather than skipping the whole check, four missing regression tests added).

Verification: full suite 794 pass / 0 fail; prompt-library-contracts 29/29; asset build green.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

@noodlemind: I will review the changes in 6483f03, including the reported regression coverage and the reworked structural-expectations behavior.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/harness/lib/structural/expectations.mjs`:
- Around line 57-63: The per-file tier gate currently treats missing
fileEntry.tier as lexical, even when index metadata declares another extractor
tier. In the expectations logic around tierSkipped and the structural index
metadata, fall back to index.meta.extractorTier when fileEntry.tier is absent,
then skip non-lexical tiers as before. Add a regression test covering an absent
file tier with meta.extractorTier set to treesitter.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9d537c32-480b-4945-b627-d61f177e2ea7

📥 Commits

Reviewing files that changed from the base of the PR and between 1ebca0e and 6483f03.

📒 Files selected for processing (34)
  • docs/MEMORY-MODEL.md
  • docs/architecture/engineer-harness.md
  • knowledge/proposals/harness-evolution-blueprint.md
  • packages/harness/lib/commands.mjs
  • packages/harness/lib/context-pack.mjs
  • packages/harness/lib/events.mjs
  • packages/harness/lib/flags.mjs
  • packages/harness/lib/knowledge/admin.mjs
  • packages/harness/lib/knowledge/apply.mjs
  • packages/harness/lib/knowledge/consolidate.mjs
  • packages/harness/lib/knowledge/layer.mjs
  • packages/harness/lib/knowledge/overlay.mjs
  • packages/harness/lib/knowledge/promote.mjs
  • packages/harness/lib/knowledge/prune.mjs
  • packages/harness/lib/knowledge/store.mjs
  • packages/harness/lib/policy.mjs
  • packages/harness/lib/repo-map/index.mjs
  • packages/harness/lib/repo-map/structural-index.mjs
  • packages/harness/lib/repo-map/treesitter-extractor.mjs
  • packages/harness/lib/report.mjs
  • packages/harness/lib/structural/expectations.mjs
  • packages/harness/lib/structural/shape.mjs
  • packages/harness/test/doctor-structural.test.mjs
  • packages/harness/test/hand-edits.test.mjs
  • packages/harness/test/knowledge-promote.test.mjs
  • packages/harness/test/layer-maintenance.test.mjs
  • packages/harness/test/layer-routing.test.mjs
  • packages/harness/test/layered-overlay.test.mjs
  • packages/harness/test/report.test.mjs
  • packages/harness/test/store-migration.test.mjs
  • packages/harness/test/structural-expectations.test.mjs
  • packages/harness/test/structural-index.test.mjs
  • packages/harness/test/structural-shape-compat.test.mjs
  • packages/harness/test/treesitter-extractor.test.mjs
🚧 Files skipped from review as they are similar to previous changes (25)
  • docs/architecture/engineer-harness.md
  • packages/harness/lib/knowledge/overlay.mjs
  • packages/harness/test/doctor-structural.test.mjs
  • packages/harness/lib/flags.mjs
  • packages/harness/lib/report.mjs
  • packages/harness/test/structural-shape-compat.test.mjs
  • packages/harness/lib/context-pack.mjs
  • packages/harness/lib/policy.mjs
  • packages/harness/test/layer-maintenance.test.mjs
  • packages/harness/lib/repo-map/index.mjs
  • packages/harness/lib/knowledge/promote.mjs
  • packages/harness/lib/knowledge/prune.mjs
  • packages/harness/lib/events.mjs
  • packages/harness/lib/knowledge/layer.mjs
  • packages/harness/test/layered-overlay.test.mjs
  • packages/harness/lib/commands.mjs
  • packages/harness/lib/structural/shape.mjs
  • packages/harness/lib/knowledge/store.mjs
  • packages/harness/lib/knowledge/consolidate.mjs
  • packages/harness/test/structural-expectations.test.mjs
  • packages/harness/lib/repo-map/treesitter-extractor.mjs
  • packages/harness/lib/knowledge/admin.mjs
  • packages/harness/lib/repo-map/structural-index.mjs
  • packages/harness/test/layer-routing.test.mjs
  • packages/harness/lib/knowledge/apply.mjs

Comment thread packages/harness/lib/structural/expectations.mjs Outdated
@noodlemind
noodlemind force-pushed the claude/harness-evolution-proposals-tqgkw6 branch from 93db07c to 67100ef Compare August 6, 2026 13:41
@noodlemind

Copy link
Copy Markdown
Owner Author

Independent review round — 9 P1s found and fixed

I ran my own three-lens adversarial review (knowledge-layer correctness, security, structural/verify) over the full branch diff, deliberately excluding the themes the earlier automated round already closed. It found nine P1-class defects, all cross-module or contract-level. Two reviewers independently converged on two of them. All are now fixed across three commits, with regression tests that fail before and pass after.

Promotion lane could mint protected, primitive-eligible golden knowledge

  • Evidence laundering: re-validation matched on path@sha256 only and ignored kind, while episodeLines defaults unknown kinds to fix. One --insight episode listed three times became "3 verified fixes across 3 plans" — defeating the documented "insights can never reach T2→T3 promotion" control and conferring permanent protection from supersession. Fixed: episode kind/plan are re-derived from the source learning; duplicate path@sha256 within an op is rejected.
  • Emitter-only admission gates: promote.mjs refused detached buckets, non-ancestor buckets, unsafe keys, and inactive sources; applyOps — the sole writer — re-checked none. Fixed: every gate is re-derived at write time (ancestry read from the bucket's own meta, never the envelope), each tested through consolidate --apply with hand-authored ops files and valid digests.
  • Extra defect found during the fix: a NOOP inside a promotion envelope consumed episodes into the golden ledger from any branch. Promotion op-sets are now restricted to ADD/STRENGTHEN/SUPERSEDE.

Verification integrity

severity: advisory didn't just soften an exit code — it rewrote outcome to passed, which is what gate and compound trust. A policy marking scope advisory opened the gate on real violations and minted the "verified" evidence that feeds the laundering above. Fixed: twelve built-in gating checks cannot be downgraded, enforced by a drift test that scans verify.mjs's own check ids. Advisory checks are also no longer counted as failures or offered as the next fix target.

Layer boundary leaked in both directions

Hand-deleting a branch-local learning wrote a store-wide retire, permanently retiring the protected golden claim of the same name; --layer golden was self-grantable by any agent. Fixed: governance retire is recorded only when no layer holds the id (mirroring purge); --layer golden requires human presence or --yes; a branch-lane remember can no longer cancel a golden veto; promoted_to_golden now survives every re-render (it was dropped by renderLearning, resurrecting promoted claims).

Structural index

--since never checked the ref against the prior index's meta.sha, stamping stale entries as current HEAD; removed-symbol-with-callers was unreachable in every real configuration (tier-gated out, and the lexical tier hard-coded exported: false) while its tests used a fixture shape the builder cannot emit; the index was keyed per-remote so co-located worktrees served each other's tables. Fixed: --since narrows only when aligned with the prior baseline (reported when ignored); real export detection plus explicit-import references in the lexical tier so the finding works in the default grammar-free config, with fixtures rebuilt from real builder output; index keyed per worktree.

Also fixed: unredacted uncapped repo text reaching evidence twice; the wasm loader's JS entry point now hash-pinned alongside the wasm (a missing lock is a loud refusal, not a silent disable); quarantine made store-global (branch-switching no longer resets three-strikes); prune previews and refuses to delete active unpromoted learnings without --yes; the quadratic lexical extractor (658 ms → 9 ms on a 2 MB file); silent cap truncation now recorded and degrading findings to informational; doctor S1 no longer hard-fails on a foreign web-tree-sitter.

Deferred, documented not hidden: branch-local learnings remain unreachable from learnings --why / learning retire (noted as a known gap in MEMORY-MODEL with the workarounds that do exist).

Verification: full suite 843 pass / 0 fail (from 794); prompt-library-contracts 29/29; asset build green; TDD-verified — the new boundary tests fail against the pre-fix library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants