SDK Context Integrity

Tracker: pm-0k19l7, pm-9stazf, pm-tu71, pm-0xmajx, pm-7rrqsk, pm-ety1qc, pm-lu6sca, pm-5y05kq, pm-gjjurs, pm-h97qxd, pm-h06944, pm-5t33or, pm-in23qu, pm-h8tpeh, pm-okgxwa, pm-22rzjp, pm-76fkpp, pm-igdvfq, pm-643e0k, pm-larv4r, pm-mcxk8v, pm-2zkvxm, and pm-ea1yh2.

Current closure tranche: pm-fs8q9x, pm-gy885b, and pm-f05lsg.

Agent Quick Context

These contracts keep project management equal to context management: reads say what they omit, writes return only newly useful context, diagnostics do not unexpectedly call remote providers, and every transport delegates domain validation to the same SDK primitive. Package authors can use the same primitives without reproducing CLI parsing rules.

get output selectors

pm get has one declared selector namespace. Top-level sections use their names, while item fields may be written as either a bare field or item.<field>:

pm get pm-a1b2 --output-include id,title
pm get pm-a1b2 --output-include item.id,item.title,linked
pm get pm-a1b2 --output-include item,claim_state
pm get pm-a1b2 --output-include comments,learnings,tests

An unknown selector is a usage refusal that lists the valid vocabulary. Selecting the complete item object together with an item field is also refused because the two selectors express conflicting projection depths. Every successful projection carries an omission_receipt with the exact selectors needed to restore withheld item fields or sections.

Collection selectors participate in the same pre-execution projection on CLI, SDK, and MCP transports. Requesting comments, notes, learnings, files, tests, docs, reminders, or events therefore loads only the named item collections before the universal output layer removes unrequested fields.

Automatic receipts cover every heavy item collection (comments, notes, learnings, files, tests, docs, reminders, and events) plus body, children, claim_state, linked, and schedule. Empty included collections are distinguishable from omitted collections because inclusion is derived from property presence, not collection length.

Standard and brief item reads expose the stable collection_counts selector; full reads retain those counts and normalize every supported collection key to an array. --output-include item.collection_counts therefore uses the same selector grammar and omission receipts as any other SDK-owned item field.

The same SDK-owned read-output registry now declares package manage as a first-class read surface. Package authors can resolve either package manage or package-manage to its canonical contract and discover the universal include, amount, cost, and encoding dimensions without copying CLI knowledge.

Compatibility aliases declare whether their migration promise is an exact replacement or only behavior_preserving. Exact projection modes are available through readOutputIncludeModeOptions() and are executed against their legacy spelling in the temporary-tracker contract suite. Execution controls such as deps --collapse <value> and health --check-only retain their independent semantics and are never exposed as false include-mode replacements.

Every registered read surface also resolves a format-aware default token ceiling from the public command-output contract. Results already inside that ceiling are returned byte-for-byte unchanged, so the safety default adds no receipt overhead to ordinary reads. Oversized results follow the shared compaction ladder and identify budget_source: default plus the applied budget_tokens in their read_output receipt. Complete internal artifacts and callers that intentionally accept unbounded output must say so explicitly with --output-budget unbounded or outputBudget: "unbounded"; that opt-out is distinct from row-count --unbounded compatibility behavior.

The budget ladder separately discovers nested arrays below declared result rows. This lets governance envelopes reduce diagnostic findings before omitting the whole verdict without redefining a nested tag or remediation list as a pagination row. Receipts name every compacted_row_paths entry; a complete omission also records the useful result's pre-omission estimate, while the recovery names a bounded budget increase when no declared row path can resume.

Budget-compacted declared rows instead publish a bounded outputCursor continuation on every transport. The cursor resumes the first withheld row and rejects a command, path, row-total, or stable-identity mismatch. This gives SDK and package loops a recursive self-improvement primitive: consume a bounded page, update context, and continue without abandoning the ceiling.

context --explain-ranking projects explanations to the focus rows actually served. candidate_count records the scorer population and omitted_count records explanations intentionally withheld, while each returned row retains its rank, baseline rank, score, and per-signal contributions. Explanation cost therefore scales with the answer rather than with the active workspace.

Unknown-option recovery is likewise derived from the declared flag lexicon. The structured option_scope is declared_on_path, declared_elsewhere, or declared_nowhere; accepting command paths are included only for the second case, while the third names the nearest current-path spellings and explicitly terminates the otherwise-unbounded command search.

Retry-safe annotation mutations

Comments, notes, and learnings expose one SDK-owned ifAbsent append contract. The item writer lock compares the resolved author and exact normalized stored text, so concurrent retries create one entry and one history event. The winning append reports changed: true and mutation_receipt.changed_count: 1; later exact retries return the existing entry with changed: false and changed_count: 0. Default appends remain duplicate-preserving. CLI --if-absent and MCP ifAbsent are thin transports, and --full-history remains the explicit escape hatch from bounded mutation receipts.

Semantic flag and spelling contracts

listPmFlagLexicon() classifies flags by meaning rather than spelling alone. For example, --limit is the shared result-row-limit concept, while --node-limit, --edge-limit, --output-limit, --output-budget, and --token-budget retain distinct graph, serialization, and intent-budget semantics. Command-local overloads such as --file are explicitly separated into linked-file, annotation-input, and plan-definition concepts.

listPmFlagSpellingInventory() publishes every canonical spelling and every accepted compatibility alias. The repository gate compares it with scripts/release/flag-spelling-baseline.json; removing either a canonical flag or an established alias fails closed. A separate generated help baseline records UTF-8 size, estimated tokens, and per-command deltas, so a vocabulary change cannot silently expand the agent discovery surface. Refreshing either baseline is an explicit reviewed operation through node scripts/release/flag-lexicon-gate.mjs --update-inventory.

Closed-domain refusal contracts

listCoreClosedDomainContracts() is the SDK-owned registry for every built-in --for intent and --fields projection refusal. It derives projection values from the list, get, and search query modules and covers all eight list-family entrypoints. CLI help uses the same registry, representing item.<field> and runtime-schema extensions with compact generative notation instead of repeating every alias.

The executable gate derives its refusal corpus from this registry, requires the exact error code, complete allowed_values, shell-free suggested_retry_args, and a successful retry, then compares probe identities with scripts/release/refusal-closure-baseline.json. Adding a new core domain without a probe or removing historical coverage therefore fails CI.

Executable recovery-reference coverage

Structured refusal guidance is a forward-reference contract, not decorative text. verifyPmRecoveryReferences accepts obligations derived from real error envelopes and observations produced by executing a retry, resolving a declared command path, or linking prose to an already executed recovery. Its report includes the total obligation set, pass fraction, sorted findings, and stable coverage buckets for suggested_retry, candidate_command, example, next_step, migration_hint, and restore_with, including zero-population buckets. derivePmRecoveryReferenceObligations walks emitted envelopes and generated read-output contracts, so tests cannot silently omit a producer family. Each obligation declares recovery, replacement, or behavior_preserving semantics and its proof must demonstrate the same promise.

verifyPmRecoveryKindRuntimeCoverage joins the syntax-aware complete source census to distinct emitted values for every typed kind. Its receipt retains both denominators and fails when a kind has source producers but no runtime evidence, preventing a representative recovery example from masking an entirely unexecuted producer family. This is explicitly kind-level assurance: it does not claim that every individual source location or every distinct value was executed. The former verifyPmRecoveryProducerRuntimeCoverage export remains as a deprecated compatibility alias with those same kind-level semantics.

The repository integration corpus drives real CLI refusals, executes the emitted retry in a temporary tracker, compares candidate commands with the unbounded runtime contract enumeration, and binds examples and next steps to those proofs. A seeded unreachable observation must lower the pass fraction and produce unreachable_reference. The same corpus is a provider-backed blocking assertion in repository-context-quality, so a well-formed but broken recovery promise cannot pass CI merely because its text or target flag exists.

Row discovery and exact output receipts

Row locations and encoding capabilities remain part of the SDK-owned read contract, but the row_contract discovery block is opt-in on rendered command results. Use --output-row-contract in the CLI or outputRowContract: true in SDK and MCP options when a generic consumer must discover row selectors. Normal agent reads omit the repeated metadata and retain the same internal projections.

Uniform flat object arrays use canonical tabular TOON when TOON output is selected. Mixed, nested, or scalar collections retain the general recursive encoding, so package authors can add richer shapes without pretending they are tabular. The optional row contract declares toon_encoding: tabular_when_uniform for consumers that negotiate this optimization.

When discovery metadata is omitted, every context_intent, read_output, and read_session estimate is stabilized against the final serialized envelope. Consequently estimated_tokens and spent_this_call_tokens never charge an agent for hidden row metadata, and a caller-carried output session remains an exact cross-command budget rather than an approximation of an intermediate shape.

Bounded annotation mutations

Adding, editing, or deleting a comment, note, or learning returns the changed entry plus mutation and omission receipts. The reply size therefore stays independent of the item’s existing annotation history. Pass --full-history when a human or integration genuinely needs the complete post-mutation collection:

pm comments pm-a1b2 "Decision evidence"
pm comments pm-a1b2 --edit 3 --message "Corrected evidence" --full-history
pm notes pm-a1b2 --delete 2 --full-history

SDK callers use fullHistory: true; MCP callers use full: true. The default stays bounded on every transport. An omission receipt identifies the semantic full_history selector and includes its CLI (--full-history), SDK (fullHistory), and MCP (full) spellings, so non-CLI consumers never need to interpret shell-only guidance.

Author acknowledgment coordinates

CLI, SDK, and MCP use the same selector and coordinate parser for history-author-acknowledge. A coordinate is <item-id>:<line> or _workspace:<line>, with a positive one-based line number. Exactly one of explicit events or all_actionable is required. Applying is a two-step operation: dry-run returns a deterministic SHA-256 over the complete, ordered coordinate set and every exact source-line hash; apply requires that fingerprint and refuses stale source or selection drift with CONFLICT exit 4 before writing history.

pm history-author-acknowledge \
  --event _workspace:4 \
  --dry-run

pm history-author-acknowledge \
  --event _workspace:4 \
  --plan-fingerprint <sha256-from-preview> \
  --attributed-author import-agent \
  --reviewer maintainer \
  --reason "Verified workspace provenance"

Preview coordinates are bounded with --limit, but the fingerprint and counts always cover the complete selection. Plans distinguish already acknowledged coordinates, and apply reports effect, no_effect (exit 6), or partial_effect (exit 7). Bulk selection appends only newly actionable rows, while explicit coordinates may append a superseding disposition as described below. The SDK exposes planUnknownAuthorHistoryAcknowledgment, resolveUnknownAuthorAcknowledgmentSelector, and parseUnknownAuthorHistoryEventCoordinates so packages never need a private copy of this grammar. Health and validate map actionable unknown-author warnings directly to this append-only acknowledgment command instead of sending callers through another diagnostic loop.

An explicit --event selection is also the append-only correction path: if a coordinate already has a disposition, preview marks it already_acknowledged, and fingerprint-bound apply appends a later disposition that supersedes the earlier attribution. Recover the original coordinates from context.author_acknowledgment.events in the applicable history/_workspace.jsonl record, preview those same coordinates, and apply the fresh fingerprint with the corrected author and review evidence. By contrast, --all-actionable intentionally selects only undispositioned rows; an empty bulk selection remains no_effect and never repeats existing dispositions.

Health provider boundary

pm health is read-only by default and never refreshes embeddings merely because a semantic provider is configured. Provider I/O requires --refresh-vectors; --skip-vectors or --no-refresh records the explicit non-provider path. Provider requests remain bounded by the configured embedding timeout, and a failed refresh reports the responsible vector diagnostic plus the skip remediation.

Storage integrity is evaluated independently of that provider boundary. Lossless merge receipts remain visible as provenance, while only receipts containing discarded scalar values produce merge_decisions_unreviewed guidance; neither classification enables vector refresh or remote provider I/O.

Default history-drift verification also replays the append-only _workspace stream and compares every governed singleton with its latest recorded state. That agreement check reads only local history and JSON files: mismatched, missing, or unreadable singleton paths become bounded history_drift evidence without enabling embeddings, vector-store access, or any other provider I/O.

Every SDK operation that requires a valid _workspace chain refuses the same way when verification fails. The typed workspace_history_chain_invalid conflict includes verification_errors in CLI JSON and SDK error context plus an executable pm history _workspace --verify --json recovery command. This keeps deliberate corruption tests and recoverable tracker drift out of the unexpected-runtime-error channel without weakening the fail-closed mutation boundary.

Brief and summary check-only health projections use the scalar-only metadata reader. Validation uses collection-bearing metadata for evidence and relationship checks and materializes bodies only when strict history-drift verification is requested.

Every health check row exposes both its tri-state status and a required boolean ok. The boolean is exactly status === "ok" in full, brief, and summary projections, so generic SDK and package consumers can use a stable success predicate without discarding warning-versus-error detail.

The storage check also reads at most 10,000 local immutable events for bounded agent-provenance resolver outcomes. This scan performs no network or provider I/O, tolerates malformed streams already owned by integrity diagnostics, and reports an advisory warning only when a resolver was actually attempted but never succeeded. The same bounded pass classifies bare boolean and single-digit values across every recorded provenance dimension, publishes only aggregate harness/dimension/kind counts, and never echoes the historical value.

historyRepair and historyRepairAll retain the detected history hash epoch instead of unconditionally upgrading it. normalizeProvenance: true adds a privacy-safe repair mode that removes invalid bounded observations and records only aggregate counts/classes in the synthetic audit event. The bulk form selects both drifted streams and streams requiring provenance normalization.

Health results are self-indexing: every retained warning has a findings row with its owning check, stable code, severity, and either executable remediation or an explicit safe refusal. failed_because contains the exact warning tokens that decide ok: false and survives brief/summary projection. Extension health adds a host-version census by resolving the pm-cli package visible from each loaded extension; npm/pnpm duplicate-version skew is gate-failing while matching workspace-linked and deduplicated copies remain green.

Linked file/doc add parsing uses one SDK allowlist. Whitespace-free filesystem paths and scheme-prefixed URLs remain positional even when a path or query contains =. Leading assignment syntax, including malformed URL-plus-note input, must use the declared path, scope, and note keys or fail before mutation.

Replication and refusal gate

scripts/release/surface-replication-sets.json declares replicated SDK, CLI, MCP, documentation, and test members. pnpm quality:surface-replication activates sets from the Git changeset, verifies every member invariant, and reports:

  • active set recurrence density;
  • the largest source member’s utilization of the mandatory file-size cap;
  • every remaining CLI-owned PmCliError, grouped by an explicit adapter-level disposition;
  • applied waivers, including their PM owner and expiry;
  • an AST-derived denominator of identical named rule bodies, declared coverage, and a non-decreasing detected-cluster floor.

Trigger entries may constrain a shared file with changed_lines_contain_any. The set then activates only when an added or removed diff line carries one of those contract markers. Missing diff evidence fails closed and activates the set, while an unrelated hunk in the same shared table does not force artificial edits across every replicated member.

Query waivers directly with:

node scripts/release/surface-replication-gate.mjs --list-waivers

Waivers are never implicit: they require a canonical PM item, a reason, an exact set member, and an expiry date. New or moved CLI refusals fail until the inventory is updated or the rule is delegated into the SDK. The same declaration runs locally and inside the required static-quality workflow.


Sdk_context_integrity remote
Ein Problem melden