fix: bound collection writes to what they change and scope blocker clears
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 filesandpm docswrites 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-indexedits.- 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 sameomission_receipt. The omission receipt carries thefull_historyselector. --full-history(SDKfullHistory, MCPfull) returns the whole collection after a write. List reads are unchanged.failure_categoriesappears only when linked tests ran.- The implicit post-write path check validates the returned links.
--validate-pathsstill checks every stored path. - A
pm test --addon 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:
projectLinkedCollectionMutationandwrittenLinkedEntries.
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.
detailscarriesmisread_segment,misread_key,noteand the underlyingcause.recovery.suggested_retry_argsholds 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)
parseCsvKvstored one value per key, sopath=a.txt,note=moved here, path: b.txtsilently linked b.txt, andpath=d.txt,path=e.txtkept 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_byand--blocked-by noneno longer delete everyblocked_byedge. They remove only the rows naming the scalar's own target: the literal text, or the canonical row a--blocked-bywrite 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_byfor 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
- pm-usn5k2 is closed: its last open criterion, unbraind/pm-vcs#179, merged on 2026-11-04.
Verification
- Static chain: the full
quality:staticchain (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,
mainfailed 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.
- The SDK entrypoint import benchmark failed under host load. Run back-to-back under the same load,
- 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.tsis fully covered. - Live sandbox checks: I executed every emitted recovery command. All note-recovery retries link the full note, and the kind-wide
--dep-removeand scoped--unset blocked_bybehave as documented. - Tracker gates: graph composition (prose-edge gaps at the 1233 ceiling), record integrity, context quality and defect evidence all pass.
changelog:pm:checkis 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, andrunDocswrite receipts are a breaking signature change; migration guidance is indocs/SDK.md.