fix: bound collection writes to what they change and scope blocker clears

Merged
UN
17h ago
+2785-351

Summary

Collection mutations now change and report only what they target. This bundles five tracker items on one seam: writes to linked tests, files, docs and dependency rows, plus the shared structured-entry grammar they parse.

Bounded linked-collection receipts — pm-gh1700 (GH-1700)

  • pm test, pm files and pm docs writes now return only the entries they added or rewrote, instead of re-sending the whole stored list. Writes are --add, --add-json, --add-glob, --remove, --remove-index, --migrate, and linked-test --update/--update-index edits.
  • The receipt shape matches comments, notes and learnings: count (entries returned), total_count (entries stored), mutation_receipt (changed_count, removed_count, full_history_included) and the same omission_receipt. The omission receipt carries the full_history selector.
  • --full-history (SDK fullHistory, MCP full) returns the whole collection after a write. List reads are unchanged.
  • failure_categories appears only when linked tests ran.
  • The implicit post-write path check validates the returned links. --validate-paths still checks every stored path.
  • A pm test --add on an item with 26 linked tests now returns 900 B; the issue measured 3,335 B. A spec proves the size no longer grows with the list.
  • A measurement refusal's recovery now replays --full-history, so the retry keeps the caller's receipt shape.
  • SDK surface: acknowledged breaking (signature), with migration guidance in docs/SDK.md. Before tightening, I checked what the released fleet reads: pm-vcs and pm-web read stored lists only from list calls.
  • New SDK primitives: projectLinkedCollectionMutation and writtenLinkedEntries.

Misread note recovery — pm-pwqiao

Note prose such as note=see, host:port opens a key after the comma. The files/docs --add refusal for this is now linked_artifact_note_key_misread:

  • The message names the misread segment and key.
  • details carries misread_segment, misread_key, note and the underlying cause.
  • recovery.suggested_retry_args holds the same link with the prose moved to --note, which takes it verbatim. I executed the retry for all three variants (generic, unknown-key, repeated-key); each links the file with the full note.
  • The GH-258 typo refusal (lable=main) and every other refusal are unchanged.

Repeated keys are refused — pm-oglm0t (new, found while fixing pm-pwqiao)

  • parseCsvKv stored one value per key, so path=a.txt,note=moved here, path: b.txt silently linked b.txt, and path=d.txt,path=e.txt kept only e.txt.
  • Both grammar branches now refuse a repeated key with structured_entry_key_repeated.
  • The comment, note and learning seed fallbacks store such prose verbatim instead of keeping only the last value.

Scoped blocker clear — pm-4yxzvd (P1)

  • pm update --unset blocked_by and --blocked-by none no longer delete every blocked_by edge. They remove only the rows naming the scalar's own target: the literal text, or the canonical row a --blocked-by write derived. An independent prerequisite keeps blocking.
  • --dep-remove kind=<kind> is the explicit kind-wide removal.
  • Every update that removes dependency rows names them in a dependencies_removed:<item>:<id>/<kind>,... warning.
  • The start refusal now offers --unset blocked_by for multi-edge items.
  • The close-time pending-unblock recovery becomes strictly safer: a concurrently added blocker keeps its edge. Its docs are corrected.

Package headroom — pm-tuxw1i (pm-998juj tranche)

  • Two repository-specific docs (DEVELOPMENT_DEPENDENCY_SECURITY.md, CONTEXT_ALGORITHM_PORTFOLIO.md) are excluded from the npm tarball. Packaged links now use canonical URLs.
  • Headroom was 949 B after the previous release; a clean build now has about 32 KB.

Tracker reconciliation

Verification

  • Static chain: the full quality:static chain (minus mutation testing) passes locally. Two gates need a note:
    • The SDK entrypoint import benchmark failed under host load. Run back-to-back under the same load, main failed it by a similar or larger margin, so the regression is not from this branch.
    • The package-artifact gate passes at 19,990,126 / 20,000,000 B, and the Sentry-injected simulation passes.
  • Full unit suite: 12,518 tests passed and 5 failed on the first full run. Four were expectations of the old shapes and are updated in this PR. The fifth, composed-continuation, is the known local-only failure: Homebrew Node ships no npm.
  • Coverage: scoped coverage of every changed source file, intersected with the diff's added lines, shows no uncovered statement, branch or function. The new dependency-removal.ts is fully covered.
  • Live sandbox checks: I executed every emitted recovery command. All note-recovery retries link the full note, and the kind-wide --dep-remove and scoped --unset blocked_by behave as documented.
  • Tracker gates: graph composition (prose-edge gaps at the 1233 ceiling), record integrity, context quality and defect evidence all pass. changelog:pm:check is in sync.

Closes #1700


Summary by cubic

Collection mutations now change and report only what they target. pm test, pm files, and pm docs writes return the entries they added or rewrote with comments-style receipts (count, total_count, mutation_receipt, omission_receipt); --full-history (SDK fullHistory, MCP full) returns the whole collection after a write. A 26-test add drops from 3,335 B to 900 B and no longer grows with the list. failure_categories appears only when linked tests ran. Receipts diff the persisted collection before and after a write, so stored-order results never report a draft spelling, and removed_count counts only stored tests with no same-identity entry left after the write. An --add-glob that matches nothing gets a zero-change receipt instead of falling into list mode.

Blocker clears are scoped: --unset blocked_by and --blocked-by none remove only rows naming the scalar's own target, and --dep-remove kind=<kind> alone is the explicit kind-wide removal, refusing kind and its type alias together; an empty id= is refused rather than widened. Updates that remove dependency rows name them in dependencies_removed warnings, the start refusal offers the scalar clear for multi-edge items, and pending-unblock recovery keeps concurrently added blockers.

Structured entries now refuse a repeated key (structured_entry_key_repeated) instead of silently keeping the last value; note prose that opens a key after a comma is refused as linked_artifact_note_key_misread, with a retry replaying the full invocation with the prose moved to --note. The retry replays active revision expectations as --expect-hash; when those guards cannot be spelled as one invocation, it withholds the retry and names the rewritten entry. Escaped commas in link fields survive because segments keep their raw source, and one splitter backs both the parser and the misread-note diagnosis.

The Sentry release gate now treats an event as test traffic when its environment is test or its pm.source_context is test, so a test child delivering with SENTRY_ENVIRONMENT=ci no longer blocks a cut. Five repository-only docs and the generated docs/performance JSON reports are excluded from the npm tarball, keeping the base artifact gate at 19,985,206 of 20,000,000 B.

Migration

  • SDK runTest, runFiles, and runDocs write receipts are a breaking signature change; migration guidance is in docs/SDK.md.